search_youtube/
lib.rs

1use std::fmt::Display;
2
3use clap::{Parser, ValueEnum};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Deserialize, Serialize, Clone)]
7pub struct YoutubeSearchResponse {
8    pub kind: String,
9    pub etag: String,
10    #[serde(rename = "pageInfo")]
11    pub page_info: PageInfo,
12    pub items: Vec<YoutubeItem>,
13}
14
15#[derive(Debug, Deserialize, Serialize, Clone)]
16pub struct PageInfo {
17    pub total_results: Option<i64>,
18    pub results_per_page: Option<i64>,
19}
20
21#[derive(Debug, Deserialize, Serialize, Clone)]
22pub struct YoutubeItem {
23    pub kind: String,
24    pub etag: String,
25    pub id: YoutubeId,
26    pub snippet: YoutubeSnippet,
27}
28
29#[derive(Debug, Deserialize, Serialize, Clone)]
30pub struct YoutubeId {
31    pub kind: String,
32    #[serde(rename = "videoId")]
33    pub video_id: Option<String>,
34    #[serde(rename = "channelId")]
35    pub channel_id: Option<String>,
36}
37
38#[derive(Debug, Deserialize, Serialize, Clone)]
39pub struct YoutubeSnippet {
40    #[serde(rename = "publishedAt")]
41    pub published_at: String,
42    pub title: String,
43    pub description: String,
44    #[serde(rename = "channelTitle")]
45    pub channel_title: String,
46    #[serde(rename = "publishTime")]
47    pub publish_time: String,
48}
49
50#[derive(Debug, Serialize, Clone, Parser)]
51pub struct YoutubeSearchRequest {
52    #[clap(
53        short,
54        long,
55        help = "The part parameter specifies a comma-separated list of one or more search resource properties that the API response will include",
56        default_value = "snippet"
57    )]
58    pub part: Option<String>,
59    #[clap(
60        short,
61        long,
62        help = "The channel_id parameter indicates that the API response should only contain resources created by the channel",
63    )]
64    pub channel_id: Option<String>,
65    #[clap(
66        short,
67        long,
68        help = "The channel_type parameter lets you restrict a search to a particular type of channel (any or show)",
69    )]
70    pub channel_type: Option<ChannelType>,
71    #[clap(
72        short,
73        long,
74        help = "The event_type parameter restricts a search to broadcast events",
75    )]
76    pub event_type: Option<EventType>,
77    #[clap(
78        short,
79        long,
80        help = "The location parameter restricts a search to videos that have a geographical location e.g.(37.42307,-122.08427)",
81    )]
82    pub location: Option<String>,
83    #[clap(
84        short,
85        long,
86        help = "The location_radius parameter specifies the maximum distance that the location associated with a video can be from the specified location",
87    )]
88    pub location_radius: Option<String>,
89    #[clap(
90        short,
91        long,
92        help = "The max_results parameter specifies the maximum number of items that should be returned in the result set",
93        default_value = "25"
94    )]
95    pub max_results: Option<i64>,
96    #[clap(
97        short,
98        long,
99    )]
100    pub on_behalf_of_content_owner: Option<String>,
101    #[clap(
102        short,
103        long,
104        help = "The order parameter specifies the method that will be used to order resources in the API response (date, rating, relevance, title, videoCount, viewCount)",
105        default_value = "relevance"
106    )]
107    pub order: Option<Order>,
108    #[clap(
109        short,
110        long,
111        help = "The page_token parameter identifies a specific page in the result set that should be returned",
112    )]
113    pub page_token: Option<String>,
114    #[clap(
115        short,
116        long,
117        help = "The published_after parameter indicates that the API response should only contain resources created after this date (RFC 3339)",
118    )]
119    pub published_after: Option<String>,
120    #[clap(
121        short,
122        long,
123        help = "The published_before parameter indicates that the API response should only contain resources created before this date (RFC 3339)",
124    )]
125    pub published_before: Option<String>,
126    #[clap(
127        short,
128        long,
129        help = "The q parameter specifies the query term to search for e.g. (cats|dogs)",
130    )]
131    pub q: Option<String>,
132    #[clap(
133        short,
134        long,
135        help = "The region_code parameter instructs the API to return search results for the specified country",
136    )]
137    pub region_code: Option<String>,
138    #[clap(
139        short,
140        long,
141        help = "The relevance_language parameter instructs the API to return search results that are most relevant to the specified language",
142    )]
143    pub relevance_language: Option<String>,
144    #[clap(
145        short,
146        long,
147        help = "The safe_search parameter indicates whether the search results should include restricted content as well as standard content",
148        default_value = "moderate"
149    )]
150    pub safe_search: Option<SafeSearch>,
151    #[clap(
152        long,
153        help = "The topic_id parameter indicates that the API response should only contain resources associated with the specified topic",
154    )]
155    pub topic_id: Option<Topic>,
156    #[clap(
157        short,
158        long,
159        help = "The type parameter restricts a search query to only retrieve a particular type of resource (channel, playlist, video)",
160    )]
161    pub type_: Option<Type>,
162    #[clap(
163        short,
164        long,
165        help = "The video_caption parameter indicates whether the API should filter video search results based on whether they have captions",
166    )]
167    pub video_caption: Option<VideoCaption>,
168    #[clap(
169        short,
170        long,
171        help = "The video_category_id parameter filters video search results based on their category",
172    )]
173    pub video_category_id: Option<String>,
174    #[clap(
175        short,
176        long,
177        help = "The video_definition parameter lets you restrict a search to only include either high definition (HD) or standard definition (SD) videos (any, high, standard)",
178    )]
179    pub video_definition: Option<VideoDefinition>,
180    #[clap(
181        short,
182        long,
183        help = "The video_dimension parameter lets you restrict a search to only retrieve 2D or 3D videos (any, 2d, 3d)",
184    )]
185    pub video_dimension: Option<VideoDimension>,
186    #[clap(
187        short,
188        long,
189        help = "The video_duration parameter filters video search results based on their duration (any, long, medium, short)",
190    )]
191    pub video_duration: Option<VideoDuration>,
192    #[clap(
193        short,
194        long,
195        help = "The video_embeddable parameter lets you to restrict a search to only videos that can be embedded into a webpage (any, true)",
196    )]
197    pub video_embeddable: Option<VideoEmbeddable>,
198    #[clap(
199        short,
200        long,
201        help = "The video_license parameter filters search results to only include videos with a particular license (any, creativeCommon, youtube)",
202    )]
203    pub video_license: Option<VideoLicense>,
204    #[clap(
205        short,
206        long,
207        help = "The video_paid_product_placement parameter lets you to restrict a search to only videos that contain a paid product placement (any, true)",
208    )]
209    pub video_paid_product_placement: Option<VideoPaidProductPlacement>,
210    #[clap(
211        short,
212        long,
213        help = "The video_syndicated parameter lets you to restrict a search to only videos that can be played outside youtube.com (any, true)",
214    )]
215    pub video_syndicated: Option<VideoSyndicated>,
216    #[clap(
217        short,
218        long,
219        help = "The video_type parameter lets you restrict a search to a particular type of videos (any, episode, movie)",
220    )]
221    pub video_type: Option<VideoType>,
222}
223
224#[derive(Debug, Serialize, Clone, ValueEnum)]
225pub enum VideoType {
226    #[serde(rename = "any")]
227    Any,
228    #[serde(rename = "episode")]
229    Episode,
230    #[serde(rename = "movie")]
231    Movie,
232}
233
234impl Display for VideoType {
235    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
236        match self {
237            VideoType::Any => write!(f, "any"),
238            VideoType::Episode => write!(f, "episode"),
239            VideoType::Movie => write!(f, "movie"),
240        }
241    }
242}
243
244#[derive(Debug, Serialize, Clone, ValueEnum)]
245pub enum VideoSyndicated {
246    #[serde(rename = "any")]
247    Any,
248    #[serde(rename = "true")]
249    True,
250}
251
252impl Display for VideoSyndicated {
253    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
254        match self {
255            VideoSyndicated::Any => write!(f, "any"),
256            VideoSyndicated::True => write!(f, "true"),
257        }
258    }
259}
260
261#[derive(Debug, Serialize, Clone, ValueEnum)]
262pub enum VideoPaidProductPlacement {
263    #[serde(rename = "any")]
264    Any,
265    #[serde(rename = "true")]
266    True,
267}
268
269impl Display for VideoPaidProductPlacement {
270    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
271        match self {
272            VideoPaidProductPlacement::Any => write!(f, "any"),
273            VideoPaidProductPlacement::True => write!(f, "true"),
274        }
275    }
276}
277
278#[derive(Debug, Serialize, Clone, ValueEnum)]
279pub enum VideoLicense {
280    #[serde(rename = "any")]
281    Any,
282    #[serde(rename = "creativeCommon")]
283    CreativeCommon,
284    #[serde(rename = "youtube")]
285    Youtube,
286}
287
288impl Display for VideoLicense {
289    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290        match self {
291            VideoLicense::Any => write!(f, "any"),
292            VideoLicense::CreativeCommon => write!(f, "creativeCommon"),
293            VideoLicense::Youtube => write!(f, "youtube"),
294        }
295    }
296}
297
298#[derive(Debug, Serialize, Clone, ValueEnum)]
299pub enum VideoEmbeddable {
300    #[serde(rename = "any")]
301    Any,
302    #[serde(rename = "true")]
303    True,
304}
305
306impl Display for VideoEmbeddable {
307    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
308        match self {
309            VideoEmbeddable::Any => write!(f, "any"),
310            VideoEmbeddable::True => write!(f, "true"),
311        }
312    }
313}
314
315#[derive(Debug, Serialize, Clone, ValueEnum)]
316pub enum VideoDefinition {
317    #[serde(rename = "any")]
318    Any,
319    #[serde(rename = "high")]
320    High,
321    #[serde(rename = "standard")]
322    Standard,
323}
324
325impl Display for VideoDefinition {
326    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
327        match self {
328            VideoDefinition::Any => write!(f, "any"),
329            VideoDefinition::High => write!(f, "high"),
330            VideoDefinition::Standard => write!(f, "standard"),
331        }
332    }
333}
334
335#[derive(Debug, Serialize, Clone, ValueEnum)]
336pub enum VideoCaption {
337    #[serde(rename = "any")]
338    Any,
339    #[serde(rename = "closedCaption")]
340    ClosedCaption,
341    #[serde(rename = "none")]
342    None,
343}
344
345impl Display for VideoCaption {
346    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
347        match self {
348            VideoCaption::Any => write!(f, "any"),
349            VideoCaption::ClosedCaption => write!(f, "closedCaption"),
350            VideoCaption::None => write!(f, "none"),
351        }
352    }
353}
354
355#[derive(Debug, Serialize, Clone, ValueEnum)]
356pub enum Type {
357    #[serde(rename = "channel")]
358    Channel,
359    #[serde(rename = "playlist")]
360    Playlist,
361    #[serde(rename = "video")]
362    Video,
363}
364
365impl Display for Type {
366    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
367        match self {
368            Type::Channel => write!(f, "channel"),
369            Type::Playlist => write!(f, "playlist"),
370            Type::Video => write!(f, "video"),
371        }
372    }
373}
374
375#[derive(Debug, Serialize, Clone, ValueEnum)]
376pub enum VideoDuration {
377    #[serde(rename = "any")]
378    Any,
379    #[serde(rename = "long")]
380    Long,
381    #[serde(rename = "medium")]
382    Medium,
383    #[serde(rename = "short")]
384    Short,
385}
386
387impl Display for VideoDuration {
388    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
389        match self {
390            VideoDuration::Any => write!(f, "any"),
391            VideoDuration::Long => write!(f, "long"),
392            VideoDuration::Medium => write!(f, "medium"),
393            VideoDuration::Short => write!(f, "short"),
394        }
395    }
396}
397
398#[derive(Debug, Serialize, Clone, ValueEnum)]
399pub enum VideoDimension {
400    #[serde(rename = "any")]
401    Any,
402    #[serde(rename = "2d")]
403    _2d,
404    #[serde(rename = "3d")]
405    _3d,
406}
407
408impl Display for VideoDimension {
409    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
410        match self {
411            VideoDimension::Any => write!(f, "any"),
412            VideoDimension::_2d => write!(f, "2d"),
413            VideoDimension::_3d => write!(f, "3d"),
414        }
415    }
416}
417
418#[derive(Debug, Serialize, Clone, ValueEnum)]
419pub enum Topic {
420    #[serde(rename = "/m/04rlf")]
421    Music,
422    #[serde(rename = "/m/02mscn")]
423    ChristianMusic,
424    #[serde(rename = "/m/0ggq0m")]
425    ClassicalMusic,
426    #[serde(rename = "/m/01lyv")]
427    Country,
428    #[serde(rename = "/m/02lkt")]
429    ElectronicMusic,
430    #[serde(rename = "/m/0glt670")]
431    HipHopMusic,
432    #[serde(rename = "/m/05rwpb")]
433    IndependentMusic,
434    #[serde(rename = "/m/03_d0")]
435    Jazz,
436    #[serde(rename = "/m/028sqc")]
437    MusicOfAsia,
438    #[serde(rename = "/m/0g293")]
439    MusicOfLatinAmerica,
440    #[serde(rename = "/m/064t9")]
441    PopMusic,
442    #[serde(rename = "/m/06cqb")]
443    Reggae,
444    #[serde(rename = "/m/06j6l")]
445    RhythmAndBlues,
446    #[serde(rename = "/m/06by7")]
447    RockMusic,
448    #[serde(rename = "/m/0gywn")]
449    SoulMusic,
450    #[serde(rename = "/m/0bzvm2")]
451    Gaming,
452    #[serde(rename = "/m/025zzc")]
453    ActionGame,
454    #[serde(rename = "/m/02ntfj")]
455    ActionAdventureGame,
456    #[serde(rename = "/m/0b1vjn")]
457    CasualGame,
458    #[serde(rename = "/m/02hygl")]
459    MusicVideoGame,
460    #[serde(rename = "/m/04q1x3q")]
461    PuzzleVideoGame,
462    #[serde(rename = "/m/01sjng")]
463    RacingVideoGame,
464    #[serde(rename = "/m/0403l3g")]
465    RolePlayingVideoGame,
466    #[serde(rename = "/m/021bp2")]
467    SimulationVideoGame,
468    #[serde(rename = "/m/022dc6")]
469    SportsVideoGame,
470    #[serde(rename = "/m/03hf_rm")]
471    StrategyVideoGame,
472    #[serde(rename = "/m/06ntj")]
473    Sports,
474    #[serde(rename = "/m/0jm_")]
475    AmericanFootball,
476    #[serde(rename = "/m/018jz")]
477    Baseball,
478    #[serde(rename = "/m/018w8")]
479    Basketball,
480    #[serde(rename = "/m/01cgz")]
481    Boxing,
482    #[serde(rename = "/m/09xp_")]
483    Cricket,
484    #[serde(rename = "/m/02vx4")]
485    Football,
486    #[serde(rename = "/m/037hz")]
487    Golf,
488    #[serde(rename = "/m/03tmr")]
489    IceHockey,
490    #[serde(rename = "/m/01h7lh")]
491    MixedMartialArts,
492    #[serde(rename = "/m/0410tth")]
493    Motorsport,
494    #[serde(rename = "/m/07bs0")]
495    Tennis,
496    #[serde(rename = "/m/07_53")]
497    Volleyball,
498    #[serde(rename = "/m/02jjt")]
499    Entertainment,
500    #[serde(rename = "/m/09kqc")]
501    Humor,
502    #[serde(rename = "/m/02vxn")]
503    Movies,
504    #[serde(rename = "/m/05qjc")]
505    PerformingArts,
506    #[serde(rename = "/m/066wd")]
507    PreofessionalWrestling,
508    #[serde(rename = "/m/0f2f9")]
509    TvShows,
510    #[serde(rename = "/m/019_rr")]
511    Lifestyle,
512    #[serde(rename = "/m/032tl")]
513    Fashion,
514    #[serde(rename = "/m/027x7n")]
515    Fitness,
516    #[serde(rename = "/m/02wbm")]
517    Food,
518    #[serde(rename = "/m/03glg")]
519    Hobby,
520    #[serde(rename = "/m/068hy")]
521    Pets,
522    #[serde(rename = "/m/041xxh")]
523    PhysicalAttractiveness,
524    #[serde(rename = "/m/07c1v")]
525    Technology,
526    #[serde(rename = "/m/07bxq")]
527    Tourism,
528    #[serde(rename = "/m/07yv9")]
529    Vehicles,
530    #[serde(rename = "/m/098wr")]
531    Society,
532    #[serde(rename = "/m/09s1f")]
533    Business,
534    #[serde(rename = "/m/0kt51")]
535    Health,
536    #[serde(rename = "/m/01h6rj")]
537    Military,
538    #[serde(rename = "/m/05qt0")]
539    Politics,
540    #[serde(rename = "/m/06bvp")]
541    Religion,
542    #[serde(rename = "/m/01k8wb")]
543    Knowledge,
544}
545
546impl Display for Topic {
547    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
548        match self {
549            Topic::Music => write!(f, "/m/04rlf"),
550            Topic::ChristianMusic => write!(f, "/m/02mscn"),
551            Topic::ClassicalMusic => write!(f, "/m/0ggq0m"),
552            Topic::Country => write!(f, "/m/01lyv"),
553            Topic::ElectronicMusic => write!(f, "/m/02lkt"),
554            Topic::HipHopMusic => write!(f, "/m/0glt670"),
555            Topic::IndependentMusic => write!(f, "/m/05rwpb"),
556            Topic::Jazz => write!(f, "/m/03_d0"),
557            Topic::MusicOfAsia => write!(f, "/m/028sqc"),
558            Topic::MusicOfLatinAmerica => write!(f, "/m/0g293"),
559            Topic::PopMusic => write!(f, "/m/064t9"),
560            Topic::Reggae => write!(f, "/m/06cqb"),
561            Topic::RhythmAndBlues => write!(f, "/m/06j6l"),
562            Topic::RockMusic => write!(f, "/m/06by7"),
563            Topic::SoulMusic => write!(f, "/m/0gywn"),
564            Topic::Gaming => write!(f, "/m/0bzvm2"),
565            Topic::ActionGame => write!(f, "/m/025zzc"),
566            Topic::ActionAdventureGame => write!(f, "/m/02ntfj"),
567            Topic::CasualGame => write!(f, "/m/0b1vjn"),
568            Topic::MusicVideoGame => write!(f, "/m/02hygl"),
569            Topic::PuzzleVideoGame => write!(f, "/m/04q1x3q"),
570            Topic::RacingVideoGame => write!(f, "/m/01sjng"),
571            Topic::RolePlayingVideoGame => write!(f, "/m/0403l3g"),
572            Topic::SimulationVideoGame => write!(f, "/m/021bp2"),
573            Topic::SportsVideoGame => write!(f, "/m/022dc6"),
574            Topic::StrategyVideoGame => write!(f, "/m/03hf_rm"),
575            Topic::Sports => write!(f, "/m/06ntj"),
576            Topic::AmericanFootball => write!(f, "/m/0jm_"),
577            Topic::Baseball => write!(f, "/m/018jz"),
578            Topic::Basketball => write!(f, "/m/018w8"),
579            Topic::Boxing => write!(f, "/m/01cgz"),
580            Topic::Cricket => write!(f, "/m/09xp_"),
581            Topic::Football => write!(f, "/m/02vx4"),
582            Topic::Golf => write!(f, "/m/037hz"),
583            Topic::IceHockey => write!(f, "/m/03tmr"),
584            Topic::MixedMartialArts => write!(f, "/m/01h7lh"),
585            Topic::Motorsport => write!(f, "/m/0410tth"),
586            Topic::Tennis => write!(f, "/m/07bs0"),
587            Topic::Volleyball => write!(f, "/m/07_53"),
588            Topic::Entertainment => write!(f, "/m/02jjt"),
589            Topic::Humor => write!(f, "/m/09kqc"),
590            Topic::Movies => write!(f, "/m/02vxn"),
591            Topic::PerformingArts => write!(f, "/m/05qjc"),
592            Topic::PreofessionalWrestling => write!(f, "/m/066wd"),
593            Topic::TvShows => write!(f, "/m/0f2f9"),
594            Topic::Lifestyle => write!(f, "/m/019_rr"),
595            Topic::Fashion => write!(f, "/m/032tl"),
596            Topic::Fitness => write!(f, "/m/027x7n"),
597            Topic::Food => write!(f, "/m/02wbm"),
598            Topic::Hobby => write!(f, "/m/03glg"),
599            Topic::Pets => write!(f, "/m/068hy"),
600            Topic::PhysicalAttractiveness => write!(f, "/m/041xxh"),
601            Topic::Technology => write!(f, "/m/07c1v"),
602            Topic::Tourism => write!(f, "/m/07bxq"),
603            Topic::Vehicles => write!(f, "/m/07yv9"),
604            Topic::Society => write!(f, "/m/098wr"),
605            Topic::Business => write!(f, "/m/09s1f"),
606            Topic::Health => write!(f, "/m/0kt51"),
607            Topic::Military => write!(f, "/m/01h6rj"),
608            Topic::Politics => write!(f, "/m/05qt0"),
609            Topic::Religion => write!(f, "/m/06bvp"),
610            Topic::Knowledge => write!(f, "/m/01k8wb"),
611        }
612    }
613}
614
615#[derive(Debug, Serialize, Clone, ValueEnum)]
616pub enum SafeSearch {
617    #[serde(rename = "moderate")]
618    Moderate,
619    #[serde(rename = "none")]
620    None,
621    #[serde(rename = "strict")]
622    Strict,
623}
624
625impl Display for SafeSearch {
626    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
627        match self {
628            SafeSearch::Moderate => write!(f, "moderate"),
629            SafeSearch::None => write!(f, "none"),
630            SafeSearch::Strict => write!(f, "strict"),
631        }
632    }
633}
634
635#[derive(Debug, Serialize, Clone, ValueEnum)]
636pub enum Order {
637    #[serde(rename = "date")]
638    Date,
639    #[serde(rename = "rating")]
640    Rating,
641    #[serde(rename = "relevance")]
642    Relevance,
643    #[serde(rename = "title")]
644    Title,
645    #[serde(rename = "videoCount")]
646    VideoCount,
647    #[serde(rename = "viewCount")]
648    ViewCount,
649}
650
651impl Display for Order {
652    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
653        match self {
654            Order::Date => write!(f, "date"),
655            Order::Rating => write!(f, "rating"),
656            Order::Relevance => write!(f, "relevance"),
657            Order::Title => write!(f, "title"),
658            Order::VideoCount => write!(f, "videoCount"),
659            Order::ViewCount => write!(f, "viewCount"),
660        }
661    }
662}
663
664#[derive(Debug, Serialize, Clone, ValueEnum)]
665pub enum ChannelType {
666    #[serde(rename = "any")]
667    Any,
668    #[serde(rename = "show")]
669    Show,
670}
671
672impl Display for ChannelType {
673    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
674        match self {
675            ChannelType::Any => write!(f, "any"),
676            ChannelType::Show => write!(f, "show"),
677        }
678    }
679}
680
681#[derive(Debug, Serialize, Clone, ValueEnum)]
682pub enum EventType {
683    #[serde(rename = "completed")]
684    Completed,
685    #[serde(rename = "live")]
686    Live,
687    #[serde(rename = "upcoming")]
688    Upcoming,
689}
690
691impl Display for EventType {
692    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
693        match self {
694            EventType::Completed => write!(f, "completed"),
695            EventType::Live => write!(f, "live"),
696            EventType::Upcoming => write!(f, "upcoming"),
697        }
698    }
699}
700
701pub async fn search_youtube(
702    request: YoutubeSearchRequest,
703) -> Result<YoutubeSearchResponse, Box<dyn std::error::Error>> {
704    let youtube_api_key = std::env::var("YOUTUBE_API_KEY").expect("YOUTUBE_API_KEY not set");
705    let url = "https://youtube.googleapis.com/youtube/v3/search";
706    let client = reqwest::Client::new();
707    let mut params: Vec<(&str, String)> = vec![("key", youtube_api_key)];
708    if let Some(part) = request.part {
709        params.push(("part", part));
710    }
711    if let Some(channel_id) = request.channel_id {
712        params.push(("channelId", channel_id));
713    }
714    if let Some(channel_type) = request.channel_type {
715        params.push(("channelType", channel_type.to_string()));
716    }
717    if let Some(event_type) = request.event_type {
718        params.push(("eventType", event_type.to_string()));
719    }
720    if let Some(location) = request.location {
721        params.push(("location", location));
722    }
723    if let Some(location_radius) = request.location_radius {
724        params.push(("locationRadius", location_radius));
725    }
726    if let Some(max_results) = request.max_results {
727        params.push(("maxResults", max_results.to_string()));
728    }
729    if let Some(on_behalf_of_content_owner) = request.on_behalf_of_content_owner {
730        params.push(("onBehalfOfContentOwner", on_behalf_of_content_owner));
731    }
732    if let Some(order) = request.order {
733        params.push(("order", order.to_string()));
734    }
735    if let Some(page_token) = request.page_token {
736        params.push(("pageToken", page_token));
737    }
738    if let Some(published_after) = request.published_after {
739        params.push(("publishedAfter", published_after));
740    }
741    if let Some(published_before) = request.published_before {
742        params.push(("publishedBefore", published_before));
743    }
744    if let Some(q) = request.q {
745        params.push(("q", q));
746    }
747    if let Some(region_code) = request.region_code {
748        params.push(("regionCode", region_code));
749    }
750    if let Some(relevance_language) = request.relevance_language {
751        params.push(("relevanceLanguage", relevance_language));
752    }
753    if let Some(safe_search) = request.safe_search {
754        params.push(("safeSearch", safe_search.to_string()));
755    }
756    if let Some(topic_id) = request.topic_id {
757        params.push(("topicId", topic_id.to_string()));
758    }
759    if let Some(type_) = request.type_ {
760        params.push(("type", type_.to_string()));
761    }
762    if let Some(video_caption) = request.video_caption {
763        params.push(("videoCaption", video_caption.to_string()));
764    }
765    if let Some(video_category_id) = request.video_category_id {
766        params.push(("videoCategoryId", video_category_id));
767    }
768    if let Some(video_definition) = request.video_definition {
769        params.push(("videoDefinition", video_definition.to_string()));
770    }
771    if let Some(video_dimension) = request.video_dimension {
772        params.push(("videoDimension", video_dimension.to_string()));
773    }
774    if let Some(video_duration) = request.video_duration {
775        params.push(("videoDuration", video_duration.to_string()));
776    }
777    if let Some(video_embeddable) = request.video_embeddable {
778        params.push(("videoEmbeddable", video_embeddable.to_string()));
779    }
780    if let Some(video_license) = request.video_license {
781        params.push(("videoLicense", video_license.to_string()));
782    }
783    if let Some(video_paid_product_placement) = request.video_paid_product_placement {
784        params.push((
785            "videoPaidProductPlacement",
786            video_paid_product_placement.to_string(),
787        ));
788    }
789    if let Some(video_syndicated) = request.video_syndicated {
790        params.push(("videoSyndicated", video_syndicated.to_string()));
791    }
792    if let Some(video_type) = request.video_type {
793        params.push(("videoType", video_type.to_string()));
794    }
795    let response = client
796        .get(url)
797        .query(&params)
798        .send()
799        .await?;
800    let text = response.text().await?;
801    let youtube_response: YoutubeSearchResponse = serde_json::from_str(&text)?;
802    Ok(youtube_response)
803}