1use super::{parse_flex_column_item, ParseFrom, ProcessedResult, DISPLAY_POLICY};
2use crate::common::{
3 AlbumID, AlbumType, ArtistChannelID, EpisodeID, Explicit, PlaylistID, PodcastID, ProfileID,
4 SearchSuggestion, SuggestionType, TextRun, Thumbnail, VideoID,
5};
6use crate::nav_consts::{
7 BADGE_LABEL, LIVE_BADGE_LABEL, MUSIC_CARD_SHELF, MUSIC_SHELF, NAVIGATION_BROWSE_ID,
8 PLAYLIST_ITEM_VIDEO_ID, PLAY_BUTTON, SECTION_LIST, SUBTITLE, SUBTITLE2, TAB_CONTENT,
9 THUMBNAILS, TITLE_TEXT,
10};
11use crate::parse::{EpisodeDate, ParsedSongAlbum};
12use crate::process::flex_column_item_pointer;
13use crate::query::*;
14use crate::youtube_enums::PlaylistEndpointParams;
15use crate::{Error, Result};
16use const_format::concatcp;
17use filteredsearch::{
18 AlbumsFilter, ArtistsFilter, CommunityPlaylistsFilter, EpisodesFilter, FeaturedPlaylistsFilter,
19 FilteredSearch, FilteredSearchType, PlaylistsFilter, PodcastsFilter, ProfilesFilter,
20 SongsFilter, VideosFilter,
21};
22use itertools::Itertools;
23use json_crawler::{JsonCrawler, JsonCrawlerBorrowed, JsonCrawlerIterator, JsonCrawlerOwned};
24use serde::de::IntoDeserializer;
25use serde::{Deserialize, Serialize};
26
27#[cfg(test)]
28mod tests;
29
30#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
31#[non_exhaustive]
32pub struct SearchResults {
33 pub top_results: Vec<TopResult>,
34 pub artists: Vec<SearchResultArtist>,
35 pub albums: Vec<SearchResultAlbum>,
36 pub featured_playlists: Vec<SearchResultFeaturedPlaylist>,
37 pub community_playlists: Vec<SearchResultCommunityPlaylist>,
38 pub songs: Vec<SearchResultSong>,
39 pub videos: Vec<SearchResultVideo>,
40 pub podcasts: Vec<SearchResultPodcast>,
41 pub episodes: Vec<SearchResultEpisode>,
42 pub profiles: Vec<SearchResultProfile>,
43}
44#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
45pub enum TopResultType {
47 Artist,
48 Playlist,
49 Song,
50 Video,
51 Station,
52 Podcast,
53 #[serde(untagged)]
54 Album(AlbumType),
55}
56#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
57enum SearchResultType {
59 #[serde(alias = "Top result")]
60 TopResult,
61 Artists,
62 Albums,
63 #[serde(alias = "Featured playlists")]
64 FeaturedPlaylists,
65 #[serde(alias = "Community playlists")]
66 CommunityPlaylists,
67 Songs,
68 Videos,
69 Podcasts,
70 Episodes,
71 Profiles,
72}
73
74#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
75#[non_exhaustive]
76pub struct TopResult {
81 pub result_name: String,
82 pub result_type: Option<TopResultType>,
84 pub thumbnails: Vec<Thumbnail>,
85 pub artist: Option<String>,
86 pub album: Option<String>,
87 pub duration: Option<String>,
88 pub year: Option<String>,
89 pub subscribers: Option<String>,
90 pub plays: Option<String>,
91 pub publisher: Option<String>,
93 pub byline: Option<String>,
95}
96#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
97#[non_exhaustive]
98pub struct SearchResultArtist {
100 pub artist: String,
101 pub subscribers: Option<String>,
103 pub browse_id: ArtistChannelID<'static>,
104 pub thumbnails: Vec<Thumbnail>,
105}
106#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
107#[non_exhaustive]
108pub struct SearchResultPodcast {
110 pub title: String,
111 pub publisher: String,
112 pub podcast_id: PodcastID<'static>,
113 pub thumbnails: Vec<Thumbnail>,
114}
115#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
116#[non_exhaustive]
117pub struct SearchResultEpisode {
119 pub title: String,
120 pub date: EpisodeDate,
121 pub channel_name: String,
122 pub episode_id: EpisodeID<'static>,
123 pub thumbnails: Vec<Thumbnail>,
125}
126#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
127pub enum SearchResultVideo {
129 #[non_exhaustive]
130 Video {
131 title: String,
132 channel_name: String,
135 video_id: VideoID<'static>,
136 views: String,
137 length: String,
138 thumbnails: Vec<Thumbnail>,
139 },
140 #[non_exhaustive]
141 VideoEpisode {
142 title: String,
144 date: EpisodeDate,
145 channel_name: String,
146 episode_id: EpisodeID<'static>,
147 thumbnails: Vec<Thumbnail>,
149 },
150}
151
152#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
153#[non_exhaustive]
154pub struct SearchResultProfile {
156 pub title: String,
157 pub username: String,
158 pub profile_id: ProfileID<'static>,
159 pub thumbnails: Vec<Thumbnail>,
160}
161#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
162#[non_exhaustive]
163pub struct SearchResultAlbum {
165 pub title: String,
166 pub artist: String,
167 pub year: String,
168 pub explicit: Explicit,
169 pub album_id: AlbumID<'static>,
170 pub album_type: AlbumType,
171 pub thumbnails: Vec<Thumbnail>,
172}
173#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
174#[non_exhaustive]
175pub struct SearchResultSong {
176 pub title: String,
178 pub artist: String,
179 pub album: Option<ParsedSongAlbum>,
181 pub duration: String,
182 pub plays: String,
183 pub explicit: Explicit,
184 pub video_id: VideoID<'static>,
185 pub thumbnails: Vec<Thumbnail>,
186}
187#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
188#[non_exhaustive]
189pub enum SearchResultPlaylist {
191 Featured(SearchResultFeaturedPlaylist),
192 Community(SearchResultCommunityPlaylist),
193}
194#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
195#[non_exhaustive]
196pub struct SearchResultCommunityPlaylist {
198 pub title: String,
199 pub author: String,
200 pub views: String,
201 pub playlist_id: PlaylistID<'static>,
202 pub thumbnails: Vec<Thumbnail>,
203}
204#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
205#[non_exhaustive]
206pub struct SearchResultFeaturedPlaylist {
208 pub title: String,
209 pub author: String,
210 pub songs: String,
211 pub playlist_id: PlaylistID<'static>,
212 pub thumbnails: Vec<Thumbnail>,
213}
214
215fn parse_basic_search_result_from_section_list_contents(
217 mut section_list_contents: BasicSearchSectionListContents,
218) -> Result<SearchResults> {
219 let mut top_results = Vec::new();
221 let mut artists = Vec::new();
222 let mut albums = Vec::new();
223 let mut featured_playlists = Vec::new();
224 let mut community_playlists = Vec::new();
225 let mut songs = Vec::new();
226 let mut videos = Vec::new();
227 let mut podcasts = Vec::new();
228 let mut episodes = Vec::new();
229 let mut profiles = Vec::new();
230
231 let music_card_shelf = section_list_contents
232 .0
233 .try_iter_mut()?
234 .find_path(MUSIC_CARD_SHELF)
235 .ok();
236 if let Some(music_card_shelf) = music_card_shelf {
237 top_results = parse_top_results_from_music_card_shelf_contents(music_card_shelf)?
238 }
239 let results_iter = section_list_contents
240 .0
241 .try_into_iter()?
242 .filter_map(|item| item.navigate_pointer(MUSIC_SHELF).ok());
243
244 for mut category in results_iter {
245 match category.take_value_pointer::<SearchResultType>(TITLE_TEXT)? {
246 SearchResultType::TopResult => {
247 top_results = category
248 .navigate_pointer("/contents")?
249 .try_iter_mut()?
250 .filter_map(|r| parse_top_result_from_music_shelf_contents(r).transpose())
251 .collect::<Result<Vec<TopResult>>>()?;
252 }
253 SearchResultType::Artists => {
255 artists = category
256 .navigate_pointer("/contents")?
257 .try_iter_mut()?
258 .map(|r| parse_artist_search_result_from_music_shelf_contents(r))
259 .collect::<Result<Vec<SearchResultArtist>>>()?;
260 }
261 SearchResultType::Albums => {
262 albums = category
263 .navigate_pointer("/contents")?
264 .try_iter_mut()?
265 .map(|r| parse_album_search_result_from_music_shelf_contents(r))
266 .collect::<Result<Vec<SearchResultAlbum>>>()?
267 }
268 SearchResultType::FeaturedPlaylists => {
269 featured_playlists = category
270 .navigate_pointer("/contents")?
271 .try_iter_mut()?
272 .map(|r| parse_featured_playlist_search_result_from_music_shelf_contents(r))
273 .collect::<Result<Vec<SearchResultFeaturedPlaylist>>>()?
274 }
275 SearchResultType::CommunityPlaylists => {
276 community_playlists = category
277 .navigate_pointer("/contents")?
278 .try_iter_mut()?
279 .map(|r| parse_community_playlist_search_result_from_music_shelf_contents(r))
280 .collect::<Result<Vec<SearchResultCommunityPlaylist>>>()?
281 }
282 SearchResultType::Songs => {
283 songs = category
284 .navigate_pointer("/contents")?
285 .try_iter_mut()?
286 .map(|r| parse_song_search_result_from_music_shelf_contents(r))
287 .collect::<Result<Vec<SearchResultSong>>>()?
288 }
289 SearchResultType::Videos => {
290 videos = category
291 .navigate_pointer("/contents")?
292 .try_iter_mut()?
293 .filter_map(|r| {
294 parse_video_search_result_from_music_shelf_contents(r).transpose()
295 })
296 .collect::<Result<Vec<SearchResultVideo>>>()?
297 }
298 SearchResultType::Podcasts => {
299 podcasts = category
300 .navigate_pointer("/contents")?
301 .try_iter_mut()?
302 .map(|r| parse_podcast_search_result_from_music_shelf_contents(r))
303 .collect::<Result<Vec<SearchResultPodcast>>>()?
304 }
305 SearchResultType::Episodes => {
306 episodes = category
307 .navigate_pointer("/contents")?
308 .try_iter_mut()?
309 .map(|r| parse_episode_search_result_from_music_shelf_contents(r))
310 .collect::<Result<Vec<SearchResultEpisode>>>()?
311 }
312 SearchResultType::Profiles => {
313 profiles = category
314 .navigate_pointer("/contents")?
315 .try_iter_mut()?
316 .map(|r| parse_profile_search_result_from_music_shelf_contents(r))
317 .collect::<Result<Vec<SearchResultProfile>>>()?
318 }
319 }
320 }
321 Ok(SearchResults {
322 top_results,
323 artists,
324 albums,
325 featured_playlists,
326 community_playlists,
327 songs,
328 videos,
329 podcasts,
330 episodes,
331 profiles,
332 })
333}
334
335fn parse_top_results_from_music_card_shelf_contents(
336 mut music_shelf_contents: JsonCrawlerBorrowed<'_>,
337) -> Result<Vec<TopResult>> {
338 let mut results = Vec::new();
339 let result_name = music_shelf_contents.take_value_pointer(TITLE_TEXT)?;
341 let subtitle: String = music_shelf_contents.take_value_pointer(SUBTITLE)?;
342 let subtitle_2: Option<String> = music_shelf_contents.take_value_pointer(SUBTITLE2).ok();
343 let result_type_result: std::result::Result<_, serde::de::value::Error> =
347 TopResultType::deserialize(subtitle.as_str().into_deserializer());
348 let result_type = result_type_result.ok();
349 let subscribers = subtitle_2;
351 let byline = match result_type {
352 Some(_) => None,
353 None => Some(subtitle),
354 };
355 let publisher = None;
357 let artist = None;
358 let album = None;
359 let duration = None;
360 let year = None;
361 let plays = None;
362 let thumbnails: Vec<Thumbnail> = music_shelf_contents.take_value_pointer(THUMBNAILS)?;
363 let first_result = TopResult {
364 result_type,
366 subscribers,
367 thumbnails,
368 result_name,
369 publisher,
370 artist,
371 album,
372 duration,
373 year,
374 plays,
375 byline,
376 };
377 results.push(first_result);
379 if let Ok(mut contents) = music_shelf_contents.navigate_pointer("/contents") {
381 contents
382 .try_iter_mut()?
383 .filter_map(|r| parse_top_result_from_music_shelf_contents(r).transpose())
384 .try_for_each(|r| -> Result<()> {
385 results.push(r?);
386 Ok(())
387 })?;
388 }
389 Ok(results)
390}
391fn parse_top_result_from_music_shelf_contents(
393 music_shelf_contents: JsonCrawlerBorrowed<'_>,
394) -> Result<Option<TopResult>> {
395 if music_shelf_contents.path_exists("/messageRenderer") {
397 return Ok(None);
398 };
399 let mut mrlir = music_shelf_contents.navigate_pointer("/musicResponsiveListItemRenderer")?;
400 let result_name = parse_flex_column_item(&mut mrlir, 0, 0)?;
401 let flex_1_0: String = parse_flex_column_item(&mut mrlir, 1, 0)?;
404 let result_type_result: std::result::Result<_, serde::de::value::Error> =
408 TopResultType::deserialize(flex_1_0.as_str().into_deserializer());
409 let result_type = result_type_result.ok();
410 let mut subscribers = None;
412 let mut publisher = None;
413 let mut artist = None;
414 let mut album = None;
415 let mut duration = None;
416 let mut year = None;
417 let mut plays = None;
418 match result_type {
419 Some(TopResultType::Artist) => {
421 subscribers = Some(parse_flex_column_item(&mut mrlir, 1, 2)?)
422 }
423 Some(TopResultType::Album(_)) => {
424 artist = Some(parse_flex_column_item(&mut mrlir, 1, 2)?);
426 year = Some(parse_flex_column_item(&mut mrlir, 1, 4)?);
427 }
428 Some(TopResultType::Playlist) => todo!(),
429 Some(TopResultType::Song) => {
430 artist = Some(parse_flex_column_item(&mut mrlir, 1, 2)?);
431 album = Some(parse_flex_column_item(&mut mrlir, 1, 4)?);
432 duration = Some(parse_flex_column_item(&mut mrlir, 1, 6)?);
433 plays = parse_flex_column_item(&mut mrlir, 1, 8).ok();
436 }
437 Some(TopResultType::Video) => todo!(),
438 Some(TopResultType::Station) => todo!(),
439 Some(TopResultType::Podcast) => publisher = Some(parse_flex_column_item(&mut mrlir, 1, 2)?),
440 None => {
441 artist = Some(flex_1_0);
442 album = Some(parse_flex_column_item(&mut mrlir, 1, 2)?);
443 duration = Some(parse_flex_column_item(&mut mrlir, 1, 4)?);
444 plays = parse_flex_column_item(&mut mrlir, 1, 6).ok();
447 }
448 }
449 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
450 Ok(Some(TopResult {
451 result_type,
452 subscribers,
453 thumbnails,
454 result_name,
455 publisher,
456 artist,
457 album,
458 duration,
459 year,
460 plays,
461 byline: None,
462 }))
463}
464fn parse_artist_search_result_from_music_shelf_contents(
467 music_shelf_contents: JsonCrawlerBorrowed<'_>,
468) -> Result<SearchResultArtist> {
469 let mut mrlir = music_shelf_contents.navigate_pointer("/musicResponsiveListItemRenderer")?;
470 let artist = parse_flex_column_item(&mut mrlir, 0, 0)?;
471 let subscribers = parse_flex_column_item(&mut mrlir, 1, 2).ok();
472 let browse_id = mrlir.take_value_pointer(NAVIGATION_BROWSE_ID)?;
473 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
474 Ok(SearchResultArtist {
475 artist,
476 subscribers,
477 thumbnails,
478 browse_id,
479 })
480}
481fn parse_profile_search_result_from_music_shelf_contents(
484 music_shelf_contents: JsonCrawlerBorrowed<'_>,
485) -> Result<SearchResultProfile> {
486 let mut mrlir = music_shelf_contents.navigate_pointer("/musicResponsiveListItemRenderer")?;
487 let title = parse_flex_column_item(&mut mrlir, 0, 0)?;
488 let username = parse_flex_column_item(&mut mrlir, 1, 2)?;
489 let profile_id = mrlir.take_value_pointer(NAVIGATION_BROWSE_ID)?;
490 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
491 Ok(SearchResultProfile {
492 title,
493 username,
494 profile_id,
495 thumbnails,
496 })
497}
498fn parse_album_search_result_from_music_shelf_contents(
501 music_shelf_contents: JsonCrawlerBorrowed<'_>,
502) -> Result<SearchResultAlbum> {
503 let mut mrlir = music_shelf_contents.navigate_pointer("/musicResponsiveListItemRenderer")?;
504 let title = parse_flex_column_item(&mut mrlir, 0, 0)?;
505 let album_type = parse_flex_column_item(&mut mrlir, 1, 0)?;
506
507 let (artist, year) = mrlir
510 .borrow_pointer(format!("{}/text/runs", flex_column_item_pointer(1)))?
511 .try_expect(
512 "album result should contain 3 string fields delimited by ' • '",
513 |flex_column_1| {
514 Ok(flex_column_1
515 .try_iter_mut()?
516 .skip(2)
519 .map(|mut field| field.take_value_pointer::<String>("/text"))
520 .collect::<json_crawler::CrawlerResult<String>>()?
521 .split(" • ")
522 .map(ToString::to_string)
523 .collect_tuple::<(String, String)>())
524 },
525 )?;
526
527 let explicit = if mrlir.path_exists(BADGE_LABEL) {
528 Explicit::IsExplicit
529 } else {
530 Explicit::NotExplicit
531 };
532 let browse_id = mrlir.take_value_pointer(NAVIGATION_BROWSE_ID)?;
533 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
534 Ok(SearchResultAlbum {
535 artist,
536 thumbnails,
537 album_id: browse_id,
538 title,
539 year,
540 album_type,
541 explicit,
542 })
543}
544fn parse_song_search_result_from_music_shelf_contents(
545 music_shelf_contents: JsonCrawlerBorrowed<'_>,
546) -> Result<SearchResultSong> {
547 fn parse_song_fields(
552 mrlir: &mut impl JsonCrawler,
553 ) -> json_crawler::CrawlerResult<Option<(String, Option<ParsedSongAlbum>, String)>> {
554 let num_runs = mrlir.try_iter_mut()?.count();
556 let mut fields_vec = mrlir
557 .try_iter_mut()?
558 .map(|mut field| field.take_value_pointer::<String>("/text"))
559 .collect::<json_crawler::CrawlerResult<String>>()?
560 .rsplit(" • ")
561 .map(ToString::to_string)
562 .collect::<Vec<_>>();
563 let Some(artist) = fields_vec.pop() else {
564 return Ok(None);
565 };
566 let Some(album_or_duration) = fields_vec.pop() else {
567 return Ok(None);
568 };
569 if let Some(duration) = fields_vec.pop() {
570 let album_idx = num_runs - 3;
571 let album = ParsedSongAlbum {
572 name: album_or_duration,
573 id: mrlir.take_value_pointer(format!("/{}{}", album_idx, NAVIGATION_BROWSE_ID))?,
574 };
575 return Ok(Some((artist, Some(album), duration)));
576 }
577 Ok(Some((artist, None, album_or_duration)))
578 }
579
580 let mut mrlir = music_shelf_contents.navigate_pointer("/musicResponsiveListItemRenderer")?;
581 let title = parse_flex_column_item(&mut mrlir, 0, 0)?;
582
583 let (artist, album, duration) = mrlir
584 .borrow_pointer(format!("{}/text/runs", flex_column_item_pointer(1)))?
585 .try_expect(
586 "Song result should contain 2 or 3 string fields delimited by ' • '",
587 parse_song_fields,
588 )?;
589
590 let plays = parse_flex_column_item(&mut mrlir, 2, 0)?;
591
592 let explicit = if mrlir.path_exists(BADGE_LABEL) {
593 Explicit::IsExplicit
594 } else {
595 Explicit::NotExplicit
596 };
597 let video_id = mrlir.take_value_pointer(PLAYLIST_ITEM_VIDEO_ID)?;
598 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
599 Ok(SearchResultSong {
600 artist,
601 thumbnails,
602 title,
603 explicit,
604 plays,
605 album,
606 video_id,
607 duration,
608 })
609}
610fn parse_video_search_result_from_music_shelf_contents(
613 music_shelf_contents: JsonCrawlerBorrowed<'_>,
614) -> Result<Option<SearchResultVideo>> {
615 let mut mrlir = music_shelf_contents.navigate_pointer("/musicResponsiveListItemRenderer")?;
616 if let Ok("MUSIC_ITEM_RENDERER_DISPLAY_POLICY_GREY_OUT") = mrlir
618 .take_value_pointer::<String>(DISPLAY_POLICY)
619 .as_deref()
620 {
621 return Ok(None);
622 };
623 let title = parse_flex_column_item(&mut mrlir, 0, 0)?;
624 let first_field: String = parse_flex_column_item(&mut mrlir, 1, 0)?;
625 match first_field.as_str() {
627 "Video" => {
628 let channel_name = parse_flex_column_item(&mut mrlir, 1, 2)?;
629 let views = parse_flex_column_item(&mut mrlir, 1, 4)?;
630 let length = parse_flex_column_item(&mut mrlir, 1, 6)?;
631 let video_id = mrlir.take_value_pointer(PLAYLIST_ITEM_VIDEO_ID)?;
632 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
633 Ok(Some(SearchResultVideo::Video {
634 title,
635 channel_name,
636 views,
637 length,
638 thumbnails,
639 video_id,
640 }))
641 }
642 "Episode" => {
643 let date = EpisodeDate::Recorded {
645 date: parse_flex_column_item(&mut mrlir, 1, 2)?,
646 };
647 let channel_name = parse_flex_column_item(&mut mrlir, 1, 4)?;
648 let video_id = mrlir.take_value_pointer(PLAYLIST_ITEM_VIDEO_ID)?;
649 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
650 Ok(Some(SearchResultVideo::VideoEpisode {
651 title,
652 channel_name,
653 date,
654 thumbnails,
655 episode_id: video_id,
656 }))
657 }
658 _ => {
659 if mrlir.path_exists("/flexColumns/0/musicResponsiveListItemFlexColumnRenderer/text/runs/0/navigationEndpoint/watchEndpoint") {
661
662 let views = parse_flex_column_item(&mut mrlir, 1, 2)?;
663 let length = parse_flex_column_item(&mut mrlir, 1, 4)?;
664 let video_id = mrlir.take_value_pointer(PLAYLIST_ITEM_VIDEO_ID)?;
665 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
666 Ok(Some(SearchResultVideo::Video {
667 title,
668 channel_name: first_field,
669 views,
670 length,
671 thumbnails,
672 video_id,
673 }))
674 } else {
675 let channel_name = parse_flex_column_item(&mut mrlir, 1, 2)?;
676 let video_id = mrlir.take_value_pointer(PLAYLIST_ITEM_VIDEO_ID)?;
677 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
678 Ok(Some(SearchResultVideo::VideoEpisode {
679 title,
680 channel_name,
681 date: EpisodeDate::Recorded { date: first_field },
683 thumbnails,
684 episode_id: video_id,
685 }))
686 }
687 }
688 }
689}
690fn parse_podcast_search_result_from_music_shelf_contents(
693 music_shelf_contents: JsonCrawlerBorrowed<'_>,
694) -> Result<SearchResultPodcast> {
695 let mut mrlir = music_shelf_contents.navigate_pointer("/musicResponsiveListItemRenderer")?;
696 let title = parse_flex_column_item(&mut mrlir, 0, 0)?;
697 let publisher = parse_flex_column_item(&mut mrlir, 1, 0)?;
698 let podcast_id = mrlir.take_value_pointer(NAVIGATION_BROWSE_ID)?;
699 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
700 Ok(SearchResultPodcast {
701 title,
702 publisher,
703 podcast_id,
704 thumbnails,
705 })
706}
707fn parse_episode_search_result_from_music_shelf_contents(
710 music_shelf_contents: JsonCrawlerBorrowed<'_>,
711) -> Result<SearchResultEpisode> {
712 let mut mrlir = music_shelf_contents.navigate_pointer("/musicResponsiveListItemRenderer")?;
713 let title = parse_flex_column_item(&mut mrlir, 0, 0)?;
714 let date = if mrlir.path_exists(LIVE_BADGE_LABEL) {
715 EpisodeDate::Live
716 } else {
717 EpisodeDate::Recorded {
718 date: parse_flex_column_item(&mut mrlir, 1, 0)?,
719 }
720 };
721 let channel_name = match date {
722 EpisodeDate::Live => parse_flex_column_item(&mut mrlir, 1, 0)?,
723 EpisodeDate::Recorded { .. } => parse_flex_column_item(&mut mrlir, 1, 2)?,
724 };
725 let video_id = mrlir.take_value_pointer(PLAYLIST_ITEM_VIDEO_ID)?;
726 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
727 Ok(SearchResultEpisode {
728 title,
729 date,
730 episode_id: video_id,
731 channel_name,
732 thumbnails,
733 })
734}
735fn parse_featured_playlist_search_result_from_music_shelf_contents(
738 music_shelf_contents: JsonCrawlerBorrowed<'_>,
739) -> Result<SearchResultFeaturedPlaylist> {
740 let mut mrlir = music_shelf_contents.navigate_pointer("/musicResponsiveListItemRenderer")?;
741 let title = parse_flex_column_item(&mut mrlir, 0, 0)?;
742 let author = parse_flex_column_item(&mut mrlir, 1, 0)?;
743 let songs = parse_flex_column_item(&mut mrlir, 1, 2)?;
744 let playlist_id = mrlir.take_value_pointer(NAVIGATION_BROWSE_ID)?;
745 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
746 Ok(SearchResultFeaturedPlaylist {
747 title,
748 author,
749 playlist_id,
750 songs,
751 thumbnails,
752 })
753}
754fn parse_community_playlist_search_result_from_music_shelf_contents(
757 music_shelf_contents: JsonCrawlerBorrowed<'_>,
758) -> Result<SearchResultCommunityPlaylist> {
759 let mut mrlir = music_shelf_contents.navigate_pointer("/musicResponsiveListItemRenderer")?;
760 let title = parse_flex_column_item(&mut mrlir, 0, 0)?;
761 let author = parse_flex_column_item(&mut mrlir, 1, 0)?;
762 let views = parse_flex_column_item(&mut mrlir, 1, 2)?;
763 let playlist_id = mrlir.take_value_pointer(NAVIGATION_BROWSE_ID)?;
764 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
765 Ok(SearchResultCommunityPlaylist {
766 title,
767 author,
768 playlist_id,
769 views,
770 thumbnails,
771 })
772}
773fn parse_playlist_search_result_from_music_shelf_contents(
777 music_shelf_contents: JsonCrawlerBorrowed<'_>,
778) -> Result<SearchResultPlaylist> {
779 let mut mrlir = music_shelf_contents.navigate_pointer("/musicResponsiveListItemRenderer")?;
780 let title = parse_flex_column_item(&mut mrlir, 0, 0)?;
781 let author = parse_flex_column_item(&mut mrlir, 1, 0)?;
782 let playlist_id = mrlir.take_value_pointer(NAVIGATION_BROWSE_ID)?;
783 let playlist_params: PlaylistEndpointParams = mrlir.take_value_pointer(concatcp!(
785 PLAY_BUTTON,
786 "/playNavigationEndpoint/watchPlaylistEndpoint/params"
787 ))?;
788 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
789 let playlist = match playlist_params {
790 PlaylistEndpointParams::Featured => {
791 SearchResultPlaylist::Featured(SearchResultFeaturedPlaylist {
792 title,
793 author,
794 songs: parse_flex_column_item(&mut mrlir, 1, 2)?,
795 playlist_id,
796 thumbnails,
797 })
798 }
799 PlaylistEndpointParams::Community => {
800 SearchResultPlaylist::Community(SearchResultCommunityPlaylist {
801 title,
802 author,
803 views: parse_flex_column_item(&mut mrlir, 1, 2)?,
804 playlist_id,
805 thumbnails,
806 })
807 }
808 };
809 Ok(playlist)
810}
811
812struct SectionContentsCrawler(JsonCrawlerOwned);
814struct BasicSearchSectionListContents(JsonCrawlerOwned);
815fn section_contents_is_empty(section_contents: &mut SectionContentsCrawler) -> Result<bool> {
820 Ok(section_contents
821 .0
822 .try_iter_mut()?
823 .any(|item| item.path_exists("/itemSectionRenderer/contents/0/didYouMeanRenderer")))
824}
825fn section_list_contents_is_empty(
828 section_contents: &mut BasicSearchSectionListContents,
829) -> Result<bool> {
830 let is_empty = section_contents
831 .0
832 .try_iter_mut()?
833 .filter(|item| item.path_exists(MUSIC_CARD_SHELF) || item.path_exists(MUSIC_SHELF))
834 .count()
835 == 0;
836 Ok(is_empty)
837}
838impl<'a, S: UnfilteredSearchType> TryFrom<ProcessedResult<'a, SearchQuery<'a, S>>>
839 for BasicSearchSectionListContents
840{
841 type Error = Error;
842 fn try_from(value: ProcessedResult<SearchQuery<'a, S>>) -> Result<Self> {
843 let json_crawler: JsonCrawlerOwned = value.into();
844 let section_list_contents = json_crawler.navigate_pointer(concatcp!(
845 "/contents/tabbedSearchResultsRenderer",
846 TAB_CONTENT,
847 SECTION_LIST
848 ))?;
849 Ok(BasicSearchSectionListContents(section_list_contents))
850 }
851}
852impl<'a, F: FilteredSearchType> TryFrom<ProcessedResult<'a, SearchQuery<'a, FilteredSearch<F>>>>
853 for SectionContentsCrawler
854{
855 type Error = Error;
856 fn try_from(value: ProcessedResult<SearchQuery<'a, FilteredSearch<F>>>) -> Result<Self> {
857 let json_crawler: JsonCrawlerOwned = value.into();
858 let section_contents = json_crawler.navigate_pointer(concatcp!(
859 "/contents/tabbedSearchResultsRenderer",
860 TAB_CONTENT,
861 SECTION_LIST,
862 ))?;
863 Ok(SectionContentsCrawler(section_contents))
864 }
865}
866struct FilteredSearchMSRContents(JsonCrawlerOwned);
868impl TryFrom<SectionContentsCrawler> for FilteredSearchMSRContents {
869 type Error = Error;
870 fn try_from(value: SectionContentsCrawler) -> std::prelude::v1::Result<Self, Self::Error> {
871 let music_shelf_contents = value
872 .0
873 .try_into_iter()?
874 .find_path(concatcp!(MUSIC_SHELF, "/contents"))?;
875 Ok(FilteredSearchMSRContents(music_shelf_contents))
876 }
877}
878impl TryFrom<FilteredSearchMSRContents> for Vec<SearchResultAlbum> {
879 type Error = Error;
880 fn try_from(
881 mut value: FilteredSearchMSRContents,
882 ) -> std::prelude::v1::Result<Self, Self::Error> {
883 value
885 .0
886 .try_iter_mut()?
887 .map(|a| parse_album_search_result_from_music_shelf_contents(a))
888 .collect()
889 }
890}
891impl TryFrom<FilteredSearchMSRContents> for Vec<SearchResultProfile> {
892 type Error = Error;
893 fn try_from(
894 mut value: FilteredSearchMSRContents,
895 ) -> std::prelude::v1::Result<Self, Self::Error> {
896 value
898 .0
899 .try_iter_mut()?
900 .map(|a| parse_profile_search_result_from_music_shelf_contents(a))
901 .collect()
902 }
903}
904impl TryFrom<FilteredSearchMSRContents> for Vec<SearchResultArtist> {
905 type Error = Error;
906 fn try_from(
907 mut value: FilteredSearchMSRContents,
908 ) -> std::prelude::v1::Result<Self, Self::Error> {
909 value
911 .0
912 .try_iter_mut()?
913 .map(|a| parse_artist_search_result_from_music_shelf_contents(a))
914 .collect()
915 }
916}
917impl TryFrom<FilteredSearchMSRContents> for Vec<SearchResultSong> {
918 type Error = Error;
919 fn try_from(
920 mut value: FilteredSearchMSRContents,
921 ) -> std::prelude::v1::Result<Self, Self::Error> {
922 value
924 .0
925 .try_iter_mut()?
926 .map(|a| parse_song_search_result_from_music_shelf_contents(a))
927 .collect()
928 }
929}
930impl TryFrom<FilteredSearchMSRContents> for Vec<SearchResultVideo> {
931 type Error = Error;
932 fn try_from(
933 mut value: FilteredSearchMSRContents,
934 ) -> std::prelude::v1::Result<Self, Self::Error> {
935 value
937 .0
938 .try_iter_mut()?
939 .filter_map(|a| parse_video_search_result_from_music_shelf_contents(a).transpose())
940 .collect()
941 }
942}
943impl TryFrom<FilteredSearchMSRContents> for Vec<SearchResultEpisode> {
944 type Error = Error;
945 fn try_from(
946 mut value: FilteredSearchMSRContents,
947 ) -> std::prelude::v1::Result<Self, Self::Error> {
948 value
950 .0
951 .try_iter_mut()?
952 .map(|a| parse_episode_search_result_from_music_shelf_contents(a))
953 .collect()
954 }
955}
956impl TryFrom<FilteredSearchMSRContents> for Vec<SearchResultPodcast> {
957 type Error = Error;
958 fn try_from(
959 mut value: FilteredSearchMSRContents,
960 ) -> std::prelude::v1::Result<Self, Self::Error> {
961 value
963 .0
964 .try_iter_mut()?
965 .map(|a| parse_podcast_search_result_from_music_shelf_contents(a))
966 .collect()
967 }
968}
969impl TryFrom<FilteredSearchMSRContents> for Vec<SearchResultPlaylist> {
970 type Error = Error;
971 fn try_from(
972 mut value: FilteredSearchMSRContents,
973 ) -> std::prelude::v1::Result<Self, Self::Error> {
974 value
976 .0
977 .try_iter_mut()?
978 .map(|a| parse_playlist_search_result_from_music_shelf_contents(a))
979 .collect()
980 }
981}
982impl TryFrom<FilteredSearchMSRContents> for Vec<SearchResultCommunityPlaylist> {
983 type Error = Error;
984 fn try_from(
985 mut value: FilteredSearchMSRContents,
986 ) -> std::prelude::v1::Result<Self, Self::Error> {
987 value
989 .0
990 .try_iter_mut()?
991 .map(|a| parse_community_playlist_search_result_from_music_shelf_contents(a))
992 .collect()
993 }
994}
995impl TryFrom<FilteredSearchMSRContents> for Vec<SearchResultFeaturedPlaylist> {
996 type Error = Error;
997 fn try_from(
998 mut value: FilteredSearchMSRContents,
999 ) -> std::prelude::v1::Result<Self, Self::Error> {
1000 value
1002 .0
1003 .try_iter_mut()?
1004 .map(|a| parse_featured_playlist_search_result_from_music_shelf_contents(a))
1005 .collect()
1006 }
1007}
1008impl<'a, S: UnfilteredSearchType> ParseFrom<SearchQuery<'a, S>> for SearchResults {
1009 fn parse_from(p: ProcessedResult<SearchQuery<'a, S>>) -> crate::Result<Self> {
1010 let mut section_list_contents = BasicSearchSectionListContents::try_from(p)?;
1011 if section_list_contents_is_empty(&mut section_list_contents)? {
1012 return Ok(Self::default());
1013 }
1014 parse_basic_search_result_from_section_list_contents(section_list_contents)
1015 }
1016}
1017
1018impl<'a> ParseFrom<SearchQuery<'a, FilteredSearch<ArtistsFilter>>> for Vec<SearchResultArtist> {
1019 fn parse_from(
1020 p: ProcessedResult<SearchQuery<'a, FilteredSearch<ArtistsFilter>>>,
1021 ) -> crate::Result<Self> {
1022 let mut section_contents = SectionContentsCrawler::try_from(p)?;
1023 if section_contents_is_empty(&mut section_contents)? {
1024 return Ok(Vec::new());
1025 }
1026 FilteredSearchMSRContents::try_from(section_contents)?.try_into()
1027 }
1028}
1029impl<'a> ParseFrom<SearchQuery<'a, FilteredSearch<ProfilesFilter>>> for Vec<SearchResultProfile> {
1030 fn parse_from(
1031 p: ProcessedResult<SearchQuery<'a, FilteredSearch<ProfilesFilter>>>,
1032 ) -> crate::Result<Self> {
1033 let mut section_contents = SectionContentsCrawler::try_from(p)?;
1034 if section_contents_is_empty(&mut section_contents)? {
1035 return Ok(Vec::new());
1036 }
1037 FilteredSearchMSRContents::try_from(section_contents)?.try_into()
1038 }
1039}
1040impl<'a> ParseFrom<SearchQuery<'a, FilteredSearch<AlbumsFilter>>> for Vec<SearchResultAlbum> {
1041 fn parse_from(
1042 p: ProcessedResult<SearchQuery<'a, FilteredSearch<AlbumsFilter>>>,
1043 ) -> crate::Result<Self> {
1044 let mut section_contents = SectionContentsCrawler::try_from(p)?;
1045 if section_contents_is_empty(&mut section_contents)? {
1046 return Ok(Vec::new());
1047 }
1048 FilteredSearchMSRContents::try_from(section_contents)?.try_into()
1049 }
1050}
1051impl<'a> ParseFrom<SearchQuery<'a, FilteredSearch<SongsFilter>>> for Vec<SearchResultSong> {
1052 fn parse_from(
1053 p: ProcessedResult<SearchQuery<'a, FilteredSearch<SongsFilter>>>,
1054 ) -> crate::Result<Self> {
1055 let mut section_contents = SectionContentsCrawler::try_from(p)?;
1056 if section_contents_is_empty(&mut section_contents)? {
1057 return Ok(Vec::new());
1058 }
1059 FilteredSearchMSRContents::try_from(section_contents)?.try_into()
1060 }
1061}
1062impl<'a> ParseFrom<SearchQuery<'a, FilteredSearch<VideosFilter>>> for Vec<SearchResultVideo> {
1063 fn parse_from(
1064 p: ProcessedResult<SearchQuery<'a, FilteredSearch<VideosFilter>>>,
1065 ) -> crate::Result<Self> {
1066 let mut section_contents = SectionContentsCrawler::try_from(p)?;
1067 if section_contents_is_empty(&mut section_contents)? {
1068 return Ok(Vec::new());
1069 }
1070 FilteredSearchMSRContents::try_from(section_contents)?.try_into()
1071 }
1072}
1073impl<'a> ParseFrom<SearchQuery<'a, FilteredSearch<EpisodesFilter>>> for Vec<SearchResultEpisode> {
1074 fn parse_from(
1075 p: ProcessedResult<SearchQuery<'a, FilteredSearch<EpisodesFilter>>>,
1076 ) -> crate::Result<Self> {
1077 let mut section_contents = SectionContentsCrawler::try_from(p)?;
1078 if section_contents_is_empty(&mut section_contents)? {
1079 return Ok(Vec::new());
1080 }
1081 FilteredSearchMSRContents::try_from(section_contents)?.try_into()
1082 }
1083}
1084impl<'a> ParseFrom<SearchQuery<'a, FilteredSearch<PodcastsFilter>>> for Vec<SearchResultPodcast> {
1085 fn parse_from(
1086 p: ProcessedResult<SearchQuery<'a, FilteredSearch<PodcastsFilter>>>,
1087 ) -> crate::Result<Self> {
1088 let mut section_contents = SectionContentsCrawler::try_from(p)?;
1089 if section_contents_is_empty(&mut section_contents)? {
1090 return Ok(Vec::new());
1091 }
1092 FilteredSearchMSRContents::try_from(section_contents)?.try_into()
1093 }
1094}
1095impl<'a> ParseFrom<SearchQuery<'a, FilteredSearch<CommunityPlaylistsFilter>>>
1096 for Vec<SearchResultPlaylist>
1097{
1098 fn parse_from(
1099 p: ProcessedResult<SearchQuery<'a, FilteredSearch<CommunityPlaylistsFilter>>>,
1100 ) -> crate::Result<Self> {
1101 let mut section_contents = SectionContentsCrawler::try_from(p)?;
1102 if section_contents_is_empty(&mut section_contents)? {
1103 return Ok(Vec::new());
1104 }
1105 FilteredSearchMSRContents::try_from(section_contents)?.try_into()
1106 }
1107}
1108impl<'a> ParseFrom<SearchQuery<'a, FilteredSearch<FeaturedPlaylistsFilter>>>
1109 for Vec<SearchResultFeaturedPlaylist>
1110{
1111 fn parse_from(
1112 p: ProcessedResult<SearchQuery<'a, FilteredSearch<FeaturedPlaylistsFilter>>>,
1113 ) -> crate::Result<Self> {
1114 let mut section_contents = SectionContentsCrawler::try_from(p)?;
1115 if section_contents_is_empty(&mut section_contents)? {
1116 return Ok(Vec::new());
1117 }
1118 FilteredSearchMSRContents::try_from(section_contents)?.try_into()
1119 }
1120}
1121impl<'a> ParseFrom<SearchQuery<'a, FilteredSearch<PlaylistsFilter>>> for Vec<SearchResultPlaylist> {
1122 fn parse_from(
1123 p: ProcessedResult<SearchQuery<'a, FilteredSearch<PlaylistsFilter>>>,
1124 ) -> crate::Result<Self> {
1125 let mut section_contents = SectionContentsCrawler::try_from(p)?;
1126 if section_contents_is_empty(&mut section_contents)? {
1127 return Ok(Vec::new());
1128 }
1129 FilteredSearchMSRContents::try_from(section_contents)?.try_into()
1130 }
1131}
1132
1133impl<'a> ParseFrom<GetSearchSuggestionsQuery<'a>> for Vec<SearchSuggestion> {
1134 fn parse_from(p: ProcessedResult<GetSearchSuggestionsQuery<'a>>) -> crate::Result<Self> {
1135 let json_crawler: JsonCrawlerOwned = p.into();
1136 let mut suggestions = json_crawler
1137 .navigate_pointer("/contents/0/searchSuggestionsSectionRenderer/contents")?;
1138 let mut results = Vec::new();
1139 for mut s in suggestions.try_iter_mut()? {
1140 let mut runs = Vec::new();
1141 if let Ok(mut search_suggestion) =
1142 s.borrow_pointer("/searchSuggestionRenderer/suggestion/runs")
1143 {
1144 for mut r in search_suggestion.try_iter_mut()? {
1145 if let Ok(true) = r.take_value_pointer("/bold") {
1146 runs.push(r.take_value_pointer("/text").map(TextRun::Bold)?)
1147 } else {
1148 runs.push(r.take_value_pointer("/text").map(TextRun::Normal)?)
1149 }
1150 }
1151 results.push(SearchSuggestion::new(SuggestionType::Prediction, runs))
1152 } else {
1153 for mut r in s
1154 .borrow_pointer("/historySuggestionRenderer/suggestion/runs")?
1155 .try_iter_mut()?
1156 {
1157 if let Ok(true) = r.take_value_pointer("/bold") {
1158 runs.push(r.take_value_pointer("/text").map(TextRun::Bold)?)
1159 } else {
1160 runs.push(r.take_value_pointer("/text").map(TextRun::Normal)?)
1161 }
1162 }
1163 results.push(SearchSuggestion::new(SuggestionType::History, runs))
1164 }
1165 }
1166 Ok(results)
1167 }
1168}