diff --git a/cli/src/main.rs b/cli/src/main.rs index 6e57f8c..7e02812 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -8,7 +8,7 @@ use reqwest::{Client, ClientBuilder}; use rustypipe::{ client::RustyPipe, model::{UrlTarget, VideoId}, - param::StreamFilter, + param::{search_filter, StreamFilter}, }; use serde::Serialize; @@ -50,7 +50,7 @@ enum Commands { #[clap(long)] pretty: bool, /// Limit the number of items to fetch - #[clap(long, default_value_t = 100)] + #[clap(long, default_value_t = 20)] limit: usize, /// Channel tab #[clap(long, default_value = "videos")] @@ -65,6 +65,35 @@ enum Commands { #[clap(long)] lyrics: bool, }, + /// Search YouTube + Search { + /// Search query + query: String, + /// Output format + #[clap(long, value_parser, default_value = "json")] + format: Format, + /// Pretty-print output + #[clap(long)] + pretty: bool, + /// Limit the number of items to fetch + #[clap(long, default_value_t = 20)] + limit: usize, + /// Filter results by item type + #[clap(long)] + item_type: Option, + /// Filter results by video length + #[clap(long)] + length: Option, + /// Filter results by upload date + #[clap(long)] + date: Option, + /// Sort search resulus + #[clap(long)] + order: Option, + /// YouTube Music search filter + #[clap(long)] + music: Option, + }, } #[derive(Copy, Clone, ValueEnum)] @@ -87,6 +116,101 @@ enum CommentsOrder { Latest, } +#[derive(Copy, Clone, ValueEnum)] +enum SearchItemType { + Video, + Channel, + Playlist, +} + +#[derive(Copy, Clone, ValueEnum)] +enum SearchLength { + /// < 4min + Short, + /// 4-20min + Medium, + /// > 20min + Long, +} + +#[derive(Copy, Clone, ValueEnum)] +enum SearchUploadDate { + /// 1 hour old or newer + Hour, + /// 1 day old or newer + Day, + /// 1 week old or newer + Week, + /// 1 month old or newer + Month, + /// 1 year old or newer + Year, +} + +#[derive(Copy, Clone, ValueEnum)] +enum SearchOrder { + /// Sort by Like/Dislike ratio + Rating, + /// Sort by upload date + Date, + /// Sort by view count + Views, +} + +#[derive(Copy, Clone, ValueEnum)] +enum MusicSearchCategory { + All, + Tracks, + Videos, + Artists, + Albums, + Playlists, + PlaylistsYtm, + PlaylistsCommunity, +} + +impl From for search_filter::ItemType { + fn from(value: SearchItemType) -> Self { + match value { + SearchItemType::Video => search_filter::ItemType::Video, + SearchItemType::Channel => search_filter::ItemType::Channel, + SearchItemType::Playlist => search_filter::ItemType::Playlist, + } + } +} + +impl From for search_filter::Length { + fn from(value: SearchLength) -> Self { + match value { + SearchLength::Short => search_filter::Length::Short, + SearchLength::Medium => search_filter::Length::Medium, + SearchLength::Long => search_filter::Length::Long, + } + } +} + +impl From for search_filter::UploadDate { + fn from(value: SearchUploadDate) -> Self { + match value { + SearchUploadDate::Hour => search_filter::UploadDate::Hour, + SearchUploadDate::Day => search_filter::UploadDate::Day, + SearchUploadDate::Week => search_filter::UploadDate::Week, + SearchUploadDate::Month => search_filter::UploadDate::Month, + SearchUploadDate::Year => search_filter::UploadDate::Year, + } + } +} + +impl From for search_filter::Order { + fn from(value: SearchOrder) -> Self { + match value { + SearchOrder::Rating => search_filter::Order::Rating, + SearchOrder::Date => search_filter::Order::Date, + SearchOrder::Views => search_filter::Order::Views, + } + } +} + #[allow(clippy::too_many_arguments)] async fn download_single_video( video_id: &str, @@ -474,5 +598,74 @@ async fn main() { } } } + Commands::Search { + query, + format, + pretty, + limit, + item_type, + length, + date, + order, + music, + } => match music { + None => { + let filter = search_filter::SearchFilter::new() + .item_type_opt(item_type.map(search_filter::ItemType::from)) + .length_opt(length.map(search_filter::Length::from)) + .date_opt(date.map(search_filter::UploadDate::from)) + .sort_opt(order.map(search_filter::Order::from)); + let mut res = rp.query().search_filter(&query, &filter).await.unwrap(); + res.items.extend_limit(rp.query(), limit).await.unwrap(); + print_data(&res, format, pretty); + } + Some(MusicSearchCategory::All) => { + let res = rp.query().music_search(&query).await.unwrap(); + print_data(&res, format, pretty); + } + Some(MusicSearchCategory::Tracks) => { + let mut res = rp.query().music_search_tracks(&query).await.unwrap(); + res.items.extend_limit(rp.query(), limit).await.unwrap(); + print_data(&res, format, pretty); + } + Some(MusicSearchCategory::Videos) => { + let mut res = rp.query().music_search_videos(&query).await.unwrap(); + res.items.extend_limit(rp.query(), limit).await.unwrap(); + print_data(&res, format, pretty); + } + Some(MusicSearchCategory::Artists) => { + let mut res = rp.query().music_search_artists(&query).await.unwrap(); + res.items.extend_limit(rp.query(), limit).await.unwrap(); + print_data(&res, format, pretty); + } + Some(MusicSearchCategory::Albums) => { + let mut res = rp.query().music_search_albums(&query).await.unwrap(); + res.items.extend_limit(rp.query(), limit).await.unwrap(); + print_data(&res, format, pretty); + } + Some(MusicSearchCategory::Playlists) => { + let mut res = rp.query().music_search_playlists(&query).await.unwrap(); + res.items.extend_limit(rp.query(), limit).await.unwrap(); + print_data(&res, format, pretty); + } + Some(MusicSearchCategory::PlaylistsYtm) => { + let mut res = rp + .query() + .music_search_playlists_filter(&query, false) + .await + .unwrap(); + res.items.extend_limit(rp.query(), limit).await.unwrap(); + print_data(&res, format, pretty); + } + Some(MusicSearchCategory::PlaylistsCommunity) => { + let mut res = rp + .query() + .music_search_playlists_filter(&query, true) + .await + .unwrap(); + res.items.extend_limit(rp.query(), limit).await.unwrap(); + print_data(&res, format, pretty); + } + }, }; } diff --git a/src/param/search_filter.rs b/src/param/search_filter.rs index 71e6390..84a8034 100644 --- a/src/param/search_filter.rs +++ b/src/param/search_filter.rs @@ -133,15 +133,15 @@ impl SearchFilter { self } - /// Filter videos by entity type - pub fn item_type(mut self, entity: ItemType) -> Self { - self.item_type = Some(entity); + /// Filter videos by item type + pub fn item_type(mut self, item_type: ItemType) -> Self { + self.item_type = Some(item_type); self } - /// Filter videos by entity type - pub fn item_type_opt(mut self, entity: Option) -> Self { - self.item_type = entity; + /// Filter videos by item type + pub fn item_type_opt(mut self, item_type: Option) -> Self { + self.item_type = item_type; self } @@ -175,8 +175,8 @@ impl SearchFilter { if let Some(date) = self.date { filters.varint(1, date as u64); } - if let Some(entity) = self.item_type { - filters.varint(2, entity as u64); + if let Some(item_type) = self.item_type { + filters.varint(2, item_type as u64); } if let Some(length) = self.length { filters.varint(3, length as u64);