feat: add youtube search #8
							
								
								
									
										130
									
								
								src/engine.rs
									
									
									
									
									
								
							
							
						
						
									
										130
									
								
								src/engine.rs
									
									
									
									
									
								
							| @ -1,37 +1,15 @@ | ||||
| use crate::spotify::ContentKind; | ||||
| use crate::{spotify, youtube}; | ||||
| use chrono::{Date, Utc}; | ||||
| use std::time::Duration; | ||||
| 
 | ||||
| pub(crate) enum MusicData { | ||||
|     Track(Track), | ||||
|     Album(Album), | ||||
| } | ||||
| use crate::youtube::Video; | ||||
| use crate::{spotify, youtube, TrackInfo}; | ||||
| 
 | ||||
| #[derive(Debug, Clone)] | ||||
| pub(crate) struct Track { | ||||
|     name: String, | ||||
|     authors: Vec<Author>, | ||||
|     duration: Duration, | ||||
|     album: Option<Album>, | ||||
|     description: Option<String>, | ||||
|     lyrics: Option<String>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone)] | ||||
| pub(crate) struct Album { | ||||
|     name: String, | ||||
|     authors: Vec<Author>, | ||||
|     description: Option<String>, | ||||
|     year: Option<Date<Utc>>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone)] | ||||
| pub(crate) struct Author { | ||||
|     name: String, | ||||
| pub(crate) struct TrackItem { | ||||
|     spotify_track: Option<TrackInfo>, | ||||
|     youtube_track: Option<Vec<Video>>, | ||||
| } | ||||
| 
 | ||||
| // The enum holds all the currently supported type of Id which the engine can search for
 | ||||
| #[derive(Debug, Clone)] | ||||
| pub(crate) enum ServiceIdKind { | ||||
|     Spotify(String), | ||||
|     Youtube(String), | ||||
| @ -40,6 +18,7 @@ pub(crate) enum ServiceIdKind { | ||||
| 
 | ||||
| // This struct will allow us in the future to search, cache and store data and metadata regarding
 | ||||
| // tracks, albums and playlists
 | ||||
| #[derive(Debug, Clone)] | ||||
| pub(crate) struct MusicEngine { | ||||
|     spotify: spotify::Client, | ||||
|     youtube: youtube::Client, | ||||
| @ -63,56 +42,57 @@ impl MusicEngine { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub(crate) async fn search_by_name(&self, name: &str) { | ||||
|     pub(crate) async fn search_song_by_name(&self, name: &str) { | ||||
|         todo!("In the future it would be possible to search for all metadata on a record from this call") | ||||
|     } | ||||
| 
 | ||||
|     pub(crate) async fn search_by_id(&self, id: ServiceIdKind) -> Option<MusicData> { | ||||
|         match id { | ||||
|             ServiceIdKind::Spotify(id) => { | ||||
|                 let entry_kind = spotify::get_entry_kind(id.as_str()); | ||||
|                 match entry_kind { | ||||
|                     None => None, | ||||
|                     Some(entry) => match entry { | ||||
|                         ContentKind::Track(id) => { | ||||
|                             self.spotify.get_track(id.as_str()).await.map(|track| { | ||||
|                                 MusicData::Track(Track { | ||||
|                                     name: track.name.clone(), | ||||
|                                     authors: track | ||||
|                                         .artists | ||||
|                                         .iter() | ||||
|                                         .map(|artist| Author { | ||||
|                                             name: artist.clone(), | ||||
|                                         }) | ||||
|                                         .collect(), | ||||
|                                     duration: track.duration.clone(), | ||||
|                                     album: None,       // FIXME missing metadata
 | ||||
|                                     description: None, // FIXME missing metadata
 | ||||
|                                     lyrics: None,      // FIXME missing metadata
 | ||||
|                                 }) | ||||
|                             }) | ||||
|                         } | ||||
|                         ContentKind::Album(id) => { | ||||
|                             self.spotify.get_album(id.as_str()).await.map(|album| { | ||||
|                                 MusicData::Album(Album { | ||||
|                                     name: album.name.clone(), | ||||
|                                     authors: album | ||||
|                                         .artists | ||||
|                                         .iter() | ||||
|                                         .map(|artist| Author { | ||||
|                                             name: artist.clone(), | ||||
|                                         }) | ||||
|                                         .collect(), | ||||
|                                     description: None, // FIXME missing metadata
 | ||||
|                                     year: None,        // FIXME missing metadata
 | ||||
|                                 }) | ||||
|                             }) | ||||
|                         } | ||||
|                         _ => None, | ||||
|                     }, | ||||
|                 } | ||||
|             } | ||||
|             _ => todo!("Search type not present yet"), //TODO implement
 | ||||
|     pub(crate) async fn search_song_by_id(&self, id: &str) -> Option<TrackItem> { | ||||
|         let entry_kind = spotify::get_entry_kind(id); | ||||
|         let track_info = match entry_kind { | ||||
|             Some(entry) => match entry { | ||||
|                 ContentKind::Track(id) => self.spotify.get_track(id.as_str()).await, | ||||
|                 _ => None, | ||||
|             }, | ||||
|             None => None, | ||||
|         }; | ||||
| 
 | ||||
|         if track_info.is_some() { | ||||
|             let ti = track_info.unwrap(); | ||||
|             let youtube_search = match self | ||||
|                 .youtube | ||||
|                 .search_video( | ||||
|                     format!( | ||||
|                         "{} {}", | ||||
|                         ti.artists | ||||
|                             .get(0) | ||||
|                             .map(|artist| format!("{} -", artist)) | ||||
|                             .unwrap_or("".to_string()), | ||||
|                         ti.name | ||||
|                     ) | ||||
|                     .as_str(), | ||||
|                     None, | ||||
|                 ) | ||||
|                 .await | ||||
|             { | ||||
|                 Err(_) => None, | ||||
|                 Ok(search) => Some(search), | ||||
|             }; | ||||
| 
 | ||||
|             return Some(TrackItem { | ||||
|                 spotify_track: Some(ti), | ||||
|                 youtube_track: youtube_search.map(|search| search.items), | ||||
|             }); | ||||
|         } | ||||
|         return None; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::*; | ||||
| 
 | ||||
|     #[test] | ||||
|     fn should_search_track_by_spotify_id() { | ||||
|         todo!("Implement me!") | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -6,6 +6,7 @@ use std::any::Any; | ||||
| use std::sync::Arc; | ||||
| use std::time::Duration; | ||||
| 
 | ||||
| #[derive(Debug, Clone)] | ||||
| pub enum ContentKind { | ||||
|     Track(String), | ||||
|     Album(String), | ||||
| @ -14,17 +15,20 @@ pub enum ContentKind { | ||||
|     Episode(String), | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone)] | ||||
| pub enum PlayableKind { | ||||
|     Track(TrackInfo), | ||||
|     Podcast(EpisodeInfo), | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone)] | ||||
| pub struct TrackInfo { | ||||
|     pub(crate) name: String, | ||||
|     pub(crate) artists: Vec<String>, | ||||
|     pub(crate) duration: Duration, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone)] | ||||
| pub struct EpisodeInfo { | ||||
|     pub(crate) name: String, | ||||
|     pub(crate) show: String, | ||||
| @ -34,6 +38,7 @@ pub struct EpisodeInfo { | ||||
|     pub(crate) release_date: String, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone)] | ||||
| pub struct AlbumInfo { | ||||
|     pub(crate) name: String, | ||||
|     pub(crate) artists: Vec<String>, | ||||
| @ -41,6 +46,7 @@ pub struct AlbumInfo { | ||||
|     pub(crate) tracks: Vec<TrackInfo>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone)] | ||||
| pub struct PlaylistInfo { | ||||
|     pub(crate) name: String, | ||||
|     pub(crate) artists: Vec<String>, | ||||
| @ -70,6 +76,7 @@ impl Client { | ||||
|     } | ||||
| 
 | ||||
|     pub async fn get_track(&self, id: &str) -> Option<TrackInfo> { | ||||
|         // FIXME should we really return Option here? We're hiding a possible error or a entry not found
 | ||||
|         let track_id = match TrackId::from_id(id) { | ||||
|             Ok(track) => track, | ||||
|             Err(_e) => return None, | ||||
|  | ||||
| @ -2,21 +2,22 @@ use std::error::Error; | ||||
| use std::sync::Arc; | ||||
| 
 | ||||
| pub(crate) struct VideoSearch { | ||||
|     items: Vec<Video>, | ||||
|     pub(crate) items: Vec<Video>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone)] | ||||
| pub(crate) struct Video { | ||||
|     title: String, | ||||
|     videoId: String, | ||||
|     video_id: String, | ||||
|     author: String, | ||||
|     authorId: String, | ||||
|     authorUrl: String, | ||||
|     lengthSeconds: u64, | ||||
|     author_id: String, | ||||
|     author_url: String, | ||||
|     length_seconds: u64, | ||||
|     description: String, | ||||
|     descriptionHtml: String, | ||||
|     viewCount: u64, | ||||
|     description_html: String, | ||||
|     view_count: u64, | ||||
|     published: u64, | ||||
|     publishedText: String, | ||||
|     published_text: String, | ||||
|     live_now: bool, | ||||
|     paid: bool, | ||||
|     premium: bool, | ||||
| @ -88,16 +89,16 @@ impl Client { | ||||
|                     premium, | ||||
|                 } => Some(Video { | ||||
|                     title: title.clone(), | ||||
|                     videoId: videoId.clone(), | ||||
|                     video_id: videoId.clone(), | ||||
|                     author: author.clone(), | ||||
|                     authorId: authorId.clone(), | ||||
|                     authorUrl: authorUrl.clone(), | ||||
|                     lengthSeconds: lengthSeconds.clone(), | ||||
|                     author_id: authorId.clone(), | ||||
|                     author_url: authorUrl.clone(), | ||||
|                     length_seconds: lengthSeconds.clone(), | ||||
|                     description: description.clone(), | ||||
|                     descriptionHtml: descriptionHtml.clone(), | ||||
|                     viewCount: viewCount.clone(), | ||||
|                     description_html: descriptionHtml.clone(), | ||||
|                     view_count: viewCount.clone(), | ||||
|                     published: published.clone(), | ||||
|                     publishedText: publishedText.clone(), | ||||
|                     published_text: publishedText.clone(), | ||||
|                     live_now: liveNow.clone(), | ||||
|                     paid: paid.clone(), | ||||
|                     premium: premium.clone(), | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user