feat: add youtube search #8
| @ -12,3 +12,4 @@ pretty_env_logger = "0.4.0" | |||||||
| tokio = { version =  "1.18.2", features = ["rt-multi-thread", "macros"] } | tokio = { version =  "1.18.2", features = ["rt-multi-thread", "macros"] } | ||||||
| rspotify = { version = "0.11.5", features = ["default"]} | rspotify = { version = "0.11.5", features = ["default"]} | ||||||
| sentry = "0.26.0" | sentry = "0.26.0" | ||||||
|  | invidious = "0.2.1" | ||||||
|  | |||||||
| @ -11,6 +11,7 @@ use crate::utils::{human_readable_duration, truncate_with_dots}; | |||||||
| 
 | 
 | ||||||
| mod spotify; | mod spotify; | ||||||
| mod utils; | mod utils; | ||||||
|  | mod youtube; | ||||||
| 
 | 
 | ||||||
| static MAX_ARTISTS_CHARS: usize = 140; | static MAX_ARTISTS_CHARS: usize = 140; | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										106
									
								
								src/youtube/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								src/youtube/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,106 @@ | |||||||
|  | use invidious::asynchronous::Client; | ||||||
|  | use std::error::Error; | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Clone)] | ||||||
|  | pub(crate) struct YoutubeClient { | ||||||
|  |     client: Client, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub(crate) struct VideoSearch { | ||||||
|  |     items: Vec<Video>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub(crate) struct Video { | ||||||
|  |     title: String, | ||||||
|  |     videoId: String, | ||||||
|  |     author: String, | ||||||
|  |     authorId: String, | ||||||
|  |     authorUrl: String, | ||||||
|  |     lengthSeconds: u64, | ||||||
|  |     description: String, | ||||||
|  |     descriptionHtml: String, | ||||||
|  |     viewCount: u64, | ||||||
|  |     published: u64, | ||||||
|  |     publishedText: String, | ||||||
|  |     liveNow: bool, | ||||||
|  |     paid: bool, | ||||||
|  |     premium: bool, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type SearchSortBy = str; | ||||||
|  | 
 | ||||||
|  | const BY_RELEVANCE: &SearchSortBy = "relevance"; | ||||||
|  | const BY_RATING: &SearchSortBy = "rating"; | ||||||
|  | const BY_UPLOAD_DATE: &SearchSortBy = "upload_date"; | ||||||
|  | const BY_VIEW_COUNT: &SearchSortBy = "view_count"; | ||||||
|  | 
 | ||||||
|  | impl YoutubeClient { | ||||||
|  |     async fn new() -> YoutubeClient { | ||||||
|  |         // TODO check for a stable instance
 | ||||||
|  |         let client = Client::new(String::from("https://vid.puffyan.us")); | ||||||
|  | 
 | ||||||
|  |         YoutubeClient { client } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async fn search_video( | ||||||
|  |         &self, | ||||||
|  |         keyword: &str, | ||||||
|  |         sort_by: Option<&SearchSortBy>, | ||||||
|  |     ) -> Result<VideoSearch, Box<dyn Error>> { | ||||||
|  |         let mut query = Vec::<String>::new(); | ||||||
|  |         query.push(format!("{}={}", "q", keyword)); | ||||||
|  |         match sort_by { | ||||||
|  |             Some(sorting_type) => query.push(format!("{}={}", "sort_by", sorting_type)), | ||||||
|  |             None => {} | ||||||
|  |         } | ||||||
|  |         query.push(format!("{}={}", "type", "video")); | ||||||
|  | 
 | ||||||
|  |         let generated_query = query.join(","); | ||||||
|  |         let search = self.client.search(Some(generated_query.as_str())).await?; | ||||||
|  | 
 | ||||||
|  |         let videos: Vec<Video> = search | ||||||
|  |             .items | ||||||
|  |             .iter() | ||||||
|  |             .map(|search_result| match search_result { | ||||||
|  |                 invidious::structs::hidden::SearchItem::Video { | ||||||
|  |                     title, | ||||||
|  |                     videoId, | ||||||
|  |                     author, | ||||||
|  |                     authorId, | ||||||
|  |                     authorUrl, | ||||||
|  |                     lengthSeconds, | ||||||
|  |                     videoThumbnails, | ||||||
|  |                     description, | ||||||
|  |                     descriptionHtml, | ||||||
|  |                     viewCount, | ||||||
|  |                     published, | ||||||
|  |                     publishedText, | ||||||
|  |                     liveNow, | ||||||
|  |                     paid, | ||||||
|  |                     premium, | ||||||
|  |                 } => Some(Video { | ||||||
|  |                     title: title.clone(), | ||||||
|  |                     videoId: videoId.clone(), | ||||||
|  |                     author: author.clone(), | ||||||
|  |                     authorId: authorId.clone(), | ||||||
|  |                     authorUrl: authorUrl.clone(), | ||||||
|  |                     lengthSeconds: lengthSeconds.clone(), | ||||||
|  |                     description: description.clone(), | ||||||
|  |                     descriptionHtml: descriptionHtml.clone(), | ||||||
|  |                     viewCount: viewCount.clone(), | ||||||
|  |                     published: published.clone(), | ||||||
|  |                     publishedText: publishedText.clone(), | ||||||
|  |                     liveNow: liveNow.clone(), | ||||||
|  |                     paid: paid.clone(), | ||||||
|  |                     premium: premium.clone(), | ||||||
|  |                 }), | ||||||
|  |                 _ => None, | ||||||
|  |             }) | ||||||
|  |             .filter(|x| x.is_some()) | ||||||
|  |             .map(|x| x.unwrap()) | ||||||
|  |             .filter(|x| !x.premium && !x.paid && !x.liveNow) | ||||||
|  |             .collect(); | ||||||
|  | 
 | ||||||
|  |         Result::Ok(VideoSearch { items: videos }) | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user