1use super::{
2 DISPLAY_POLICY, ParseFrom, ProcessedResult, flex_column_item_pointer, parse_flex_column_item,
3};
4use crate::common::{
5 AlbumID, AlbumType, ArtistChannelID, ContinuationParams, EpisodeID, Explicit, PlaylistID,
6 PodcastID, SearchSuggestion, SuggestionType, TextRun, Thumbnail, UserChannelID, VideoID,
7};
8use crate::continuations::ParseFromContinuable;
9use crate::nav_consts::{
10 BADGE_LABEL, CONTINUATION_PARAMS, LIVE_BADGE_LABEL, MRLIR, MUSIC_CARD_SHELF, MUSIC_SHELF,
11 MUSIC_SHELF_CONTINUATION, NAVIGATION_BROWSE, NAVIGATION_BROWSE_ID, PAGE_TYPE, PLAY_BUTTON,
12 PLAYLIST_ITEM_VIDEO_ID, SECTION_LIST, SUBTITLE, SUBTITLE2, TAB_CONTENT, THUMBNAILS, TITLE_TEXT,
13};
14use crate::parse::{EpisodeDate, ParsedSongAlbum};
15use crate::query::search::UnfilteredSearchType;
16use crate::query::search::filteredsearch::{
17 AlbumsFilter, ArtistsFilter, CommunityPlaylistsFilter, EpisodesFilter, FeaturedPlaylistsFilter,
18 FilteredSearch, FilteredSearchType, PlaylistsFilter, PodcastsFilter, ProfilesFilter,
19 SongsFilter, VideosFilter,
20};
21use crate::query::*;
22use crate::youtube_enums::{PlaylistEndpointParams, YoutubeMusicPageType};
23use crate::{Error, Result};
24use const_format::concatcp;
25use itertools::Itertools;
26use json_crawler::{JsonCrawler, JsonCrawlerBorrowed, JsonCrawlerIterator, JsonCrawlerOwned};
27use serde::de::IntoDeserializer;
28use serde::{Deserialize, Serialize};
29
30#[cfg(test)]
31mod tests;
32
33#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
34#[non_exhaustive]
35pub struct SearchResults {
36 pub top_results: Vec<TopResult>,
37 pub artists: Vec<SearchResultArtist>,
38 pub albums: Vec<SearchResultAlbum>,
39 pub featured_playlists: Vec<SearchResultFeaturedPlaylist>,
40 pub community_playlists: Vec<BasicSearchResultCommunityPlaylist>,
41 pub songs: Vec<SearchResultSong>,
42 pub videos: Vec<SearchResultVideo>,
43 pub podcasts: Vec<SearchResultPodcast>,
44 pub episodes: Vec<SearchResultEpisode>,
45 pub profiles: Vec<SearchResultProfile>,
46}
47#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
48pub enum TopResultType {
50 Artist,
51 Playlist,
52 Song,
53 Video,
54 Station,
55 Podcast,
56 #[serde(untagged)]
57 Album(AlbumType),
58}
59#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
60enum SearchResultType {
62 #[serde(alias = "Top result")]
63 TopResult,
64 Artists,
65 Albums,
66 #[serde(alias = "Featured playlists")]
67 FeaturedPlaylists,
68 #[serde(alias = "Community playlists")]
69 CommunityPlaylists,
70 Songs,
71 Videos,
72 Podcasts,
73 Episodes,
74 Profiles,
75}
76
77#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
78#[non_exhaustive]
79pub struct TopResult {
84 pub result_name: String,
85 pub result_type: Option<TopResultType>,
87 pub thumbnails: Vec<Thumbnail>,
88 pub artist: Option<String>,
89 pub album: Option<String>,
90 pub duration: Option<String>,
91 pub year: Option<String>,
92 pub subscribers: Option<String>,
93 pub plays: Option<String>,
94 pub publisher: Option<String>,
96 pub byline: Option<String>,
98}
99#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
100#[non_exhaustive]
101pub struct SearchResultArtist {
103 pub artist: String,
104 pub subscribers: Option<String>,
106 pub browse_id: ArtistChannelID<'static>,
107 pub thumbnails: Vec<Thumbnail>,
108}
109#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
110#[non_exhaustive]
111pub struct SearchResultPodcast {
113 pub title: String,
114 pub publisher: String,
115 pub podcast_id: PodcastID<'static>,
116 pub thumbnails: Vec<Thumbnail>,
117}
118#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
119#[non_exhaustive]
120pub struct SearchResultEpisode {
122 pub title: String,
123 pub date: EpisodeDate,
124 pub channel_name: String,
125 pub episode_id: EpisodeID<'static>,
126 pub thumbnails: Vec<Thumbnail>,
128}
129#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
130pub enum SearchResultVideo {
132 #[non_exhaustive]
133 Video {
134 title: String,
135 channel_name: String,
138 video_id: VideoID<'static>,
139 views: String,
140 length: String,
141 thumbnails: Vec<Thumbnail>,
142 },
143 #[non_exhaustive]
144 VideoEpisode {
145 title: String,
147 date: EpisodeDate,
148 channel_name: String,
149 episode_id: EpisodeID<'static>,
150 thumbnails: Vec<Thumbnail>,
152 },
153}
154
155#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
156#[non_exhaustive]
157pub struct SearchResultProfile {
159 pub title: String,
160 pub username: String,
161 pub profile_id: UserChannelID<'static>,
162 pub thumbnails: Vec<Thumbnail>,
163}
164#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
165#[non_exhaustive]
166pub struct SearchResultAlbum {
168 pub title: String,
169 pub artist: String,
170 pub year: String,
171 pub explicit: Explicit,
172 pub album_id: AlbumID<'static>,
173 pub album_type: AlbumType,
174 pub thumbnails: Vec<Thumbnail>,
175}
176#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
177#[non_exhaustive]
178pub struct SearchResultSong {
179 pub title: String,
181 pub artist: String,
182 pub album: Option<ParsedSongAlbum>,
184 pub duration: String,
185 pub plays: String,
186 pub explicit: Explicit,
187 pub video_id: VideoID<'static>,
188 pub thumbnails: Vec<Thumbnail>,
189}
190#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
191#[non_exhaustive]
192pub enum SearchResultPlaylist {
195 Featured(SearchResultFeaturedPlaylist),
196 Community(SearchResultCommunityPlaylist),
197 Podcast(SearchResultPodcast),
198}
199
200#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
201#[non_exhaustive]
202pub enum BasicSearchResultCommunityPlaylist {
204 Podcast(SearchResultPodcast),
205 Playlist(SearchResultCommunityPlaylist),
206}
207
208#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
209#[non_exhaustive]
210pub struct SearchResultCommunityPlaylist {
212 pub title: String,
213 pub author: String,
214 pub views: String,
215 pub playlist_id: PlaylistID<'static>,
216 pub thumbnails: Vec<Thumbnail>,
217}
218#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
219#[non_exhaustive]
220pub struct SearchResultFeaturedPlaylist {
222 pub title: String,
223 pub author: String,
224 pub songs: String,
225 pub playlist_id: PlaylistID<'static>,
226 pub thumbnails: Vec<Thumbnail>,
227}
228
229fn parse_basic_search_result_from_section_list_contents(
231 mut section_list_contents: BasicSearchSectionListContents,
232) -> Result<SearchResults> {
233 let mut top_results = Vec::new();
235 let mut artists = Vec::new();
236 let mut albums = Vec::new();
237 let mut featured_playlists = Vec::new();
238 let mut community_playlists = Vec::new();
239 let mut songs = Vec::new();
240 let mut videos = Vec::new();
241 let mut podcasts = Vec::new();
242 let mut episodes = Vec::new();
243 let mut profiles = Vec::new();
244
245 let music_card_shelf = section_list_contents
246 .0
247 .try_iter_mut()?
248 .find_path(MUSIC_CARD_SHELF)
249 .ok();
250 if let Some(music_card_shelf) = music_card_shelf {
251 top_results = parse_top_results_from_music_card_shelf_contents(music_card_shelf)?
252 }
253 let results_iter = section_list_contents
254 .0
255 .try_into_iter()?
256 .filter_map(|item| item.navigate_pointer(MUSIC_SHELF).ok());
257
258 for mut category in results_iter {
259 match category.take_value_pointer::<SearchResultType>(TITLE_TEXT)? {
260 SearchResultType::TopResult => {
261 top_results = category
262 .navigate_pointer("/contents")?
263 .try_iter_mut()?
264 .filter_map(|r| parse_top_result_from_music_shelf_contents(r).transpose())
265 .collect::<Result<Vec<TopResult>>>()?;
266 }
267 SearchResultType::Artists => {
269 artists = category
270 .navigate_pointer("/contents")?
271 .try_iter_mut()?
272 .map(|r| parse_artist_search_result_from_music_shelf_contents(r))
273 .collect::<Result<Vec<SearchResultArtist>>>()?;
274 }
275 SearchResultType::Albums => {
276 albums = category
277 .navigate_pointer("/contents")?
278 .try_iter_mut()?
279 .map(|r| parse_album_search_result_from_music_shelf_contents(r))
280 .collect::<Result<Vec<SearchResultAlbum>>>()?
281 }
282 SearchResultType::FeaturedPlaylists => {
283 featured_playlists = category
284 .navigate_pointer("/contents")?
285 .try_iter_mut()?
286 .map(|r| parse_featured_playlist_search_result_from_music_shelf_contents(r))
287 .collect::<Result<Vec<SearchResultFeaturedPlaylist>>>()?
288 }
289 SearchResultType::CommunityPlaylists => {
290 community_playlists = category
291 .navigate_pointer("/contents")?
292 .try_iter_mut()?
293 .map(|r| {
294 parse_community_playlist_basic_search_result_from_music_shelf_contents(r)
295 })
296 .collect::<Result<Vec<BasicSearchResultCommunityPlaylist>>>()?
297 }
298 SearchResultType::Songs => {
299 songs = category
300 .navigate_pointer("/contents")?
301 .try_iter_mut()?
302 .map(|r| parse_song_search_result_from_music_shelf_contents(r))
303 .collect::<Result<Vec<SearchResultSong>>>()?
304 }
305 SearchResultType::Videos => {
306 videos = category
307 .navigate_pointer("/contents")?
308 .try_iter_mut()?
309 .filter_map(|r| {
310 parse_video_search_result_from_music_shelf_contents(r).transpose()
311 })
312 .collect::<Result<Vec<SearchResultVideo>>>()?
313 }
314 SearchResultType::Podcasts => {
315 podcasts = category
316 .navigate_pointer("/contents")?
317 .try_iter_mut()?
318 .map(|r| parse_podcast_search_result_from_music_shelf_contents(r))
319 .collect::<Result<Vec<SearchResultPodcast>>>()?
320 }
321 SearchResultType::Episodes => {
322 episodes = category
323 .navigate_pointer("/contents")?
324 .try_iter_mut()?
325 .map(|r| parse_episode_search_result_from_music_shelf_contents(r))
326 .collect::<Result<Vec<SearchResultEpisode>>>()?
327 }
328 SearchResultType::Profiles => {
329 profiles = category
330 .navigate_pointer("/contents")?
331 .try_iter_mut()?
332 .map(|r| parse_profile_search_result_from_music_shelf_contents(r))
333 .collect::<Result<Vec<SearchResultProfile>>>()?
334 }
335 }
336 }
337 Ok(SearchResults {
338 top_results,
339 artists,
340 albums,
341 featured_playlists,
342 community_playlists,
343 songs,
344 videos,
345 podcasts,
346 episodes,
347 profiles,
348 })
349}
350
351fn parse_top_results_from_music_card_shelf_contents(
352 mut music_shelf_contents: JsonCrawlerBorrowed<'_>,
353) -> Result<Vec<TopResult>> {
354 let mut results = Vec::new();
355 let result_name = music_shelf_contents.take_value_pointer(TITLE_TEXT)?;
357 let result_type = music_shelf_contents
359 .borrow_value_pointer::<TopResultType>(SUBTITLE)
360 .ok();
361 let subtitle: String = music_shelf_contents.take_value_pointer(SUBTITLE)?;
362 let subtitle_2: Option<String> = music_shelf_contents.take_value_pointer(SUBTITLE2).ok();
363 let subscribers = subtitle_2;
365 let byline = match result_type {
366 Some(_) => None,
367 None => Some(subtitle),
368 };
369 let publisher = None;
371 let artist = None;
372 let album = None;
373 let duration = None;
374 let year = None;
375 let plays = None;
376 let thumbnails: Vec<Thumbnail> = music_shelf_contents.take_value_pointer(THUMBNAILS)?;
377 let first_result = TopResult {
378 result_type,
380 subscribers,
381 thumbnails,
382 result_name,
383 publisher,
384 artist,
385 album,
386 duration,
387 year,
388 plays,
389 byline,
390 };
391 results.push(first_result);
393 if let Ok(mut contents) = music_shelf_contents.navigate_pointer("/contents") {
395 contents
396 .try_iter_mut()?
397 .filter_map(|r| parse_top_result_from_music_shelf_contents(r).transpose())
398 .try_for_each(|r| -> Result<()> {
399 results.push(r?);
400 Ok(())
401 })?;
402 }
403 Ok(results)
404}
405fn parse_top_result_from_music_shelf_contents(
407 music_shelf_contents: JsonCrawlerBorrowed<'_>,
408) -> Result<Option<TopResult>> {
409 if music_shelf_contents.path_exists("/messageRenderer") {
411 return Ok(None);
412 };
413 let mut mrlir = music_shelf_contents.navigate_pointer("/musicResponsiveListItemRenderer")?;
414 let result_name = parse_flex_column_item(&mut mrlir, 0, 0)?;
415 let flex_1_0: String = parse_flex_column_item(&mut mrlir, 1, 0)?;
418 let result_type_result: std::result::Result<_, serde::de::value::Error> =
422 TopResultType::deserialize(flex_1_0.as_str().into_deserializer());
423 let result_type = result_type_result.ok();
424 let mut subscribers = None;
426 let mut publisher = None;
427 let mut artist = None;
428 let mut album = None;
429 let mut duration = None;
430 let mut year = None;
431 let mut plays = None;
432 match result_type {
433 Some(TopResultType::Artist) => {
435 subscribers = Some(parse_flex_column_item(&mut mrlir, 1, 2)?)
436 }
437 Some(TopResultType::Album(_)) => {
438 artist = Some(parse_flex_column_item(&mut mrlir, 1, 2)?);
440 year = Some(parse_flex_column_item(&mut mrlir, 1, 4)?);
441 }
442 Some(TopResultType::Playlist) => todo!(),
443 Some(TopResultType::Song) => {
444 artist = Some(parse_flex_column_item(&mut mrlir, 1, 2)?);
445 album = Some(parse_flex_column_item(&mut mrlir, 1, 4)?);
446 duration = Some(parse_flex_column_item(&mut mrlir, 1, 6)?);
447 plays = parse_flex_column_item(&mut mrlir, 1, 8).ok();
450 }
451 Some(TopResultType::Video) => todo!(),
452 Some(TopResultType::Station) => todo!(),
453 Some(TopResultType::Podcast) => publisher = Some(parse_flex_column_item(&mut mrlir, 1, 2)?),
454 None => {
455 artist = Some(flex_1_0);
456 let flex_1_2 = parse_flex_column_item(&mut mrlir, 1, 2)?;
457 if let Ok(flex_1_4) = parse_flex_column_item(&mut mrlir, 1, 4) {
459 album = Some(flex_1_2);
460 duration = Some(flex_1_4);
461 } else {
462 duration = Some(flex_1_2);
463 }
464 plays = parse_flex_column_item(&mut mrlir, 1, 6).ok();
467 }
468 }
469 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
470 Ok(Some(TopResult {
471 result_type,
472 subscribers,
473 thumbnails,
474 result_name,
475 publisher,
476 artist,
477 album,
478 duration,
479 year,
480 plays,
481 byline: None,
482 }))
483}
484fn parse_artist_search_result_from_music_shelf_contents(
487 music_shelf_contents: JsonCrawlerBorrowed<'_>,
488) -> Result<SearchResultArtist> {
489 let mut mrlir = music_shelf_contents.navigate_pointer("/musicResponsiveListItemRenderer")?;
490 let artist = parse_flex_column_item(&mut mrlir, 0, 0)?;
491 let subscribers = parse_flex_column_item(&mut mrlir, 1, 2).ok();
492 let browse_id = mrlir.take_value_pointer(NAVIGATION_BROWSE_ID)?;
493 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
494 Ok(SearchResultArtist {
495 artist,
496 subscribers,
497 thumbnails,
498 browse_id,
499 })
500}
501fn parse_profile_search_result_from_music_shelf_contents(
504 music_shelf_contents: JsonCrawlerBorrowed<'_>,
505) -> Result<SearchResultProfile> {
506 let mut mrlir = music_shelf_contents.navigate_pointer("/musicResponsiveListItemRenderer")?;
507 let title = parse_flex_column_item(&mut mrlir, 0, 0)?;
508 let username = parse_flex_column_item(&mut mrlir, 1, 2)?;
509 let profile_id = mrlir.take_value_pointer(NAVIGATION_BROWSE_ID)?;
510 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
511 Ok(SearchResultProfile {
512 title,
513 username,
514 profile_id,
515 thumbnails,
516 })
517}
518fn parse_album_search_result_from_music_shelf_contents(
521 music_shelf_contents: JsonCrawlerBorrowed<'_>,
522) -> Result<SearchResultAlbum> {
523 let mut mrlir = music_shelf_contents.navigate_pointer("/musicResponsiveListItemRenderer")?;
524 let title = parse_flex_column_item(&mut mrlir, 0, 0)?;
525 let album_type = parse_flex_column_item(&mut mrlir, 1, 0)?;
526
527 let (artist, year) = mrlir
530 .borrow_pointer(format!("{}/text/runs", flex_column_item_pointer(1)))?
531 .try_expect(
532 "album result should contain 3 string fields delimited by ' • '",
533 |flex_column_1| {
534 Ok(flex_column_1
535 .try_iter_mut()?
536 .skip(2)
539 .map(|mut field| field.take_value_pointer::<String>("/text"))
540 .collect::<json_crawler::CrawlerResult<String>>()?
541 .split(" • ")
542 .map(ToString::to_string)
543 .collect_tuple::<(String, String)>())
544 },
545 )?;
546
547 let explicit = if mrlir.path_exists(BADGE_LABEL) {
548 Explicit::IsExplicit
549 } else {
550 Explicit::NotExplicit
551 };
552 let browse_id = mrlir.take_value_pointer(NAVIGATION_BROWSE_ID)?;
553 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
554 Ok(SearchResultAlbum {
555 artist,
556 thumbnails,
557 album_id: browse_id,
558 title,
559 year,
560 album_type,
561 explicit,
562 })
563}
564fn parse_song_search_result_from_music_shelf_contents(
565 music_shelf_contents: JsonCrawlerBorrowed<'_>,
566) -> Result<SearchResultSong> {
567 fn parse_song_fields(
572 mrlir: &mut impl JsonCrawler,
573 ) -> json_crawler::CrawlerResult<Option<(String, Option<ParsedSongAlbum>, String)>> {
574 let num_runs = mrlir.try_iter_mut()?.count();
576 let mut fields_vec = mrlir
577 .try_iter_mut()?
578 .map(|mut field| field.take_value_pointer::<String>("/text"))
579 .collect::<json_crawler::CrawlerResult<String>>()?
580 .rsplit(" • ")
581 .map(ToString::to_string)
582 .collect::<Vec<_>>();
583 let Some(artist) = fields_vec.pop() else {
584 return Ok(None);
585 };
586 let Some(album_or_duration) = fields_vec.pop() else {
587 return Ok(None);
588 };
589 if let Some(duration) = fields_vec.pop() {
590 let album_idx = num_runs - 3;
591 let album = ParsedSongAlbum {
592 name: album_or_duration,
593 id: mrlir.take_value_pointer(format!("/{album_idx}{NAVIGATION_BROWSE_ID}"))?,
594 };
595 return Ok(Some((artist, Some(album), duration)));
596 }
597 Ok(Some((artist, None, album_or_duration)))
598 }
599
600 let mut mrlir = music_shelf_contents.navigate_pointer("/musicResponsiveListItemRenderer")?;
601 let title = parse_flex_column_item(&mut mrlir, 0, 0)?;
602
603 let (artist, album, duration) = mrlir
604 .borrow_pointer(format!("{}/text/runs", flex_column_item_pointer(1)))?
605 .try_expect(
606 "Song result should contain 2 or 3 string fields delimited by ' • '",
607 parse_song_fields,
608 )?;
609
610 let plays = parse_flex_column_item(&mut mrlir, 2, 0)?;
611
612 let explicit = if mrlir.path_exists(BADGE_LABEL) {
613 Explicit::IsExplicit
614 } else {
615 Explicit::NotExplicit
616 };
617 let video_id = mrlir.take_value_pointer(PLAYLIST_ITEM_VIDEO_ID)?;
618 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
619 Ok(SearchResultSong {
620 artist,
621 thumbnails,
622 title,
623 explicit,
624 plays,
625 album,
626 video_id,
627 duration,
628 })
629}
630fn parse_video_search_result_from_music_shelf_contents(
633 music_shelf_contents: JsonCrawlerBorrowed<'_>,
634) -> Result<Option<SearchResultVideo>> {
635 let mut mrlir = music_shelf_contents.navigate_pointer("/musicResponsiveListItemRenderer")?;
636 if let Ok("MUSIC_ITEM_RENDERER_DISPLAY_POLICY_GREY_OUT") = mrlir
638 .take_value_pointer::<String>(DISPLAY_POLICY)
639 .as_deref()
640 {
641 return Ok(None);
642 };
643 let title = parse_flex_column_item(&mut mrlir, 0, 0)?;
644 let first_field: String = parse_flex_column_item(&mut mrlir, 1, 0)?;
645 match first_field.as_str() {
647 "Video" => {
648 let channel_name = parse_flex_column_item(&mut mrlir, 1, 2)?;
649 let views = parse_flex_column_item(&mut mrlir, 1, 4)?;
650 let length = parse_flex_column_item(&mut mrlir, 1, 6)?;
651 let video_id = mrlir.take_value_pointer(PLAYLIST_ITEM_VIDEO_ID)?;
652 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
653 Ok(Some(SearchResultVideo::Video {
654 title,
655 channel_name,
656 views,
657 length,
658 thumbnails,
659 video_id,
660 }))
661 }
662 "Episode" => {
663 let date = EpisodeDate::Recorded {
665 date: parse_flex_column_item(&mut mrlir, 1, 2)?,
666 };
667 let channel_name = parse_flex_column_item(&mut mrlir, 1, 4)?;
668 let video_id = mrlir.take_value_pointer(PLAYLIST_ITEM_VIDEO_ID)?;
669 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
670 Ok(Some(SearchResultVideo::VideoEpisode {
671 title,
672 channel_name,
673 date,
674 thumbnails,
675 episode_id: video_id,
676 }))
677 }
678 _ => {
679 if mrlir.path_exists("/flexColumns/0/musicResponsiveListItemFlexColumnRenderer/text/runs/0/navigationEndpoint/watchEndpoint") {
681
682 let views = parse_flex_column_item(&mut mrlir, 1, 2)?;
683 let length = parse_flex_column_item(&mut mrlir, 1, 4)?;
684 let video_id = mrlir.take_value_pointer(PLAYLIST_ITEM_VIDEO_ID)?;
685 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
686 Ok(Some(SearchResultVideo::Video {
687 title,
688 channel_name: first_field,
689 views,
690 length,
691 thumbnails,
692 video_id,
693 }))
694 } else {
695 let channel_name = parse_flex_column_item(&mut mrlir, 1, 2)?;
696 let video_id = mrlir.take_value_pointer(PLAYLIST_ITEM_VIDEO_ID)?;
697 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
698 Ok(Some(SearchResultVideo::VideoEpisode {
699 title,
700 channel_name,
701 date: EpisodeDate::Recorded { date: first_field },
703 thumbnails,
704 episode_id: video_id,
705 }))
706 }
707 }
708 }
709}
710fn parse_podcast_search_result_from_music_shelf_contents(
713 music_shelf_contents: JsonCrawlerBorrowed<'_>,
714) -> Result<SearchResultPodcast> {
715 let mut mrlir = music_shelf_contents.navigate_pointer("/musicResponsiveListItemRenderer")?;
716 let title = parse_flex_column_item(&mut mrlir, 0, 0)?;
717 let publisher = parse_flex_column_item(&mut mrlir, 1, 0)?;
718 let podcast_id = mrlir.take_value_pointer(NAVIGATION_BROWSE_ID)?;
719 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
720 Ok(SearchResultPodcast {
721 title,
722 publisher,
723 podcast_id,
724 thumbnails,
725 })
726}
727fn parse_episode_search_result_from_music_shelf_contents(
730 music_shelf_contents: JsonCrawlerBorrowed<'_>,
731) -> Result<SearchResultEpisode> {
732 let mut mrlir = music_shelf_contents.navigate_pointer("/musicResponsiveListItemRenderer")?;
733 let title = parse_flex_column_item(&mut mrlir, 0, 0)?;
734 let date = if mrlir.path_exists(LIVE_BADGE_LABEL) {
735 EpisodeDate::Live
736 } else {
737 EpisodeDate::Recorded {
738 date: parse_flex_column_item(&mut mrlir, 1, 0)?,
739 }
740 };
741 let channel_name = match date {
742 EpisodeDate::Live => parse_flex_column_item(&mut mrlir, 1, 0)?,
743 EpisodeDate::Recorded { .. } => parse_flex_column_item(&mut mrlir, 1, 2)?,
744 };
745 let video_id = mrlir.take_value_pointer(PLAYLIST_ITEM_VIDEO_ID)?;
746 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
747 Ok(SearchResultEpisode {
748 title,
749 date,
750 episode_id: video_id,
751 channel_name,
752 thumbnails,
753 })
754}
755fn parse_featured_playlist_search_result_from_music_shelf_contents(
758 music_shelf_contents: JsonCrawlerBorrowed<'_>,
759) -> Result<SearchResultFeaturedPlaylist> {
760 let mut mrlir = music_shelf_contents.navigate_pointer("/musicResponsiveListItemRenderer")?;
761 let title = parse_flex_column_item(&mut mrlir, 0, 0)?;
762 let author = parse_flex_column_item(&mut mrlir, 1, 0)?;
763 let songs = parse_flex_column_item(&mut mrlir, 1, 2)?;
764 let playlist_id = mrlir.take_value_pointer(NAVIGATION_BROWSE_ID)?;
765 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
766 Ok(SearchResultFeaturedPlaylist {
767 title,
768 author,
769 playlist_id,
770 songs,
771 thumbnails,
772 })
773}
774fn parse_community_playlist_basic_search_result_from_music_shelf_contents(
775 music_shelf_contents: JsonCrawlerBorrowed<'_>,
776) -> Result<BasicSearchResultCommunityPlaylist> {
777 let result_type: YoutubeMusicPageType = music_shelf_contents
778 .borrow_value_pointer(concatcp!(MRLIR, NAVIGATION_BROWSE, PAGE_TYPE))?;
779 let result = match result_type {
780 YoutubeMusicPageType::Podcast => BasicSearchResultCommunityPlaylist::Podcast(
781 parse_podcast_search_result_from_music_shelf_contents(music_shelf_contents)?,
782 ),
783 YoutubeMusicPageType::Playlist => BasicSearchResultCommunityPlaylist::Playlist(
784 parse_community_playlist_search_result_from_music_shelf_contents(music_shelf_contents)?,
785 ),
786 };
787 Ok(result)
788}
789fn parse_community_playlist_search_result_from_music_shelf_contents(
790 music_shelf_contents: JsonCrawlerBorrowed<'_>,
791) -> Result<SearchResultCommunityPlaylist> {
792 let mut mrlir = music_shelf_contents.navigate_pointer("/musicResponsiveListItemRenderer")?;
793 let title = parse_flex_column_item(&mut mrlir, 0, 0)?;
794 let author = parse_flex_column_item(&mut mrlir, 1, 0)?;
795 let views = parse_flex_column_item(&mut mrlir, 1, 2)?;
796 let playlist_id = mrlir.take_value_pointer(NAVIGATION_BROWSE_ID)?;
797 let thumbnails: Vec<Thumbnail> = mrlir.take_value_pointer(THUMBNAILS)?;
798 Ok(SearchResultCommunityPlaylist {
799 title,
800 author,
801 playlist_id,
802 views,
803 thumbnails,
804 })
805}
806
807fn parse_playlist_search_result_from_music_shelf_contents(
808 mut music_shelf_contents: JsonCrawlerBorrowed<'_>,
809) -> Result<SearchResultPlaylist> {
810 let result_type: YoutubeMusicPageType = music_shelf_contents
811 .borrow_value_pointer(concatcp!(MRLIR, NAVIGATION_BROWSE, PAGE_TYPE))?;
812
813 match result_type {
815 YoutubeMusicPageType::Podcast => {
816 let res = parse_podcast_search_result_from_music_shelf_contents(music_shelf_contents)?;
817 Ok(SearchResultPlaylist::Podcast(res))
818 }
819 YoutubeMusicPageType::Playlist => {
820 let playlist_params: PlaylistEndpointParams =
822 music_shelf_contents.take_value_pointer(concatcp!(
823 MRLIR,
824 PLAY_BUTTON,
825 "/playNavigationEndpoint/watchPlaylistEndpoint/params"
826 ))?;
827 let playlist = match playlist_params {
828 PlaylistEndpointParams::Featured => {
829 let res = parse_featured_playlist_search_result_from_music_shelf_contents(
830 music_shelf_contents,
831 )?;
832 SearchResultPlaylist::Featured(res)
833 }
834 PlaylistEndpointParams::Community => {
835 let res = parse_community_playlist_search_result_from_music_shelf_contents(
836 music_shelf_contents,
837 )?;
838 SearchResultPlaylist::Community(res)
839 }
840 };
841 Ok(playlist)
842 }
843 }
844}
845
846struct FilteredSearchSectionContents(JsonCrawlerOwned);
847struct FilteredSearchMusicShelfContents(JsonCrawlerOwned);
848struct BasicSearchSectionListContents(JsonCrawlerOwned);
849fn section_contents_is_empty(section_contents: &mut FilteredSearchSectionContents) -> Result<bool> {
854 Ok(section_contents
855 .0
856 .try_iter_mut()?
857 .any(|item| item.path_exists("/itemSectionRenderer/contents/0/didYouMeanRenderer")))
858}
859
860fn take_continuation_params_from_section_contents(
861 section_contents: &mut FilteredSearchSectionContents,
862) -> Result<Option<ContinuationParams<'static>>> {
863 section_contents
864 .0
865 .try_iter_mut()
866 .and_then(|contents| contents.find_path(concatcp!(MUSIC_SHELF, CONTINUATION_PARAMS)))
867 .map(|mut continuation_params| continuation_params.take_value())
868 .ok()
869 .transpose()
870 .map_err(Into::into)
871}
872fn get_filtered_search_continuation_music_shelf_contents_and_params(
873 crawler: JsonCrawlerOwned,
874) -> Result<(
875 FilteredSearchMusicShelfContents,
876 Option<ContinuationParams<'static>>,
877)> {
878 let mut music_shelf = crawler.navigate_pointer(MUSIC_SHELF_CONTINUATION)?;
879 let continuation_params = music_shelf.take_value_pointer(CONTINUATION_PARAMS).ok();
880 let contents = music_shelf.navigate_pointer("/contents")?;
881 Ok((
882 FilteredSearchMusicShelfContents(contents),
883 continuation_params,
884 ))
885}
886fn section_list_contents_is_empty(
889 section_contents: &mut BasicSearchSectionListContents,
890) -> Result<bool> {
891 let is_empty = section_contents
892 .0
893 .try_iter_mut()?
894 .filter(|item| item.path_exists(MUSIC_CARD_SHELF) || item.path_exists(MUSIC_SHELF))
895 .count()
896 == 0;
897 Ok(is_empty)
898}
899impl<'a, S: UnfilteredSearchType> TryFrom<ProcessedResult<'a, SearchQuery<'a, S>>>
900 for BasicSearchSectionListContents
901{
902 type Error = Error;
903 fn try_from(value: ProcessedResult<SearchQuery<'a, S>>) -> Result<Self> {
904 let json_crawler: JsonCrawlerOwned = value.into();
905 let section_list_contents = json_crawler.navigate_pointer(concatcp!(
906 "/contents/tabbedSearchResultsRenderer",
907 TAB_CONTENT,
908 SECTION_LIST
909 ))?;
910 Ok(BasicSearchSectionListContents(section_list_contents))
911 }
912}
913impl<'a, F: FilteredSearchType> TryFrom<ProcessedResult<'a, SearchQuery<'a, FilteredSearch<F>>>>
914 for FilteredSearchSectionContents
915{
916 type Error = Error;
917 fn try_from(value: ProcessedResult<SearchQuery<'a, FilteredSearch<F>>>) -> Result<Self> {
918 let json_crawler: JsonCrawlerOwned = value.into();
919 let section_contents = json_crawler.navigate_pointer(concatcp!(
920 "/contents/tabbedSearchResultsRenderer",
921 TAB_CONTENT,
922 SECTION_LIST,
923 ))?;
924 Ok(FilteredSearchSectionContents(section_contents))
925 }
926}
927impl TryFrom<FilteredSearchSectionContents> for FilteredSearchMusicShelfContents {
928 type Error = Error;
929 fn try_from(
930 value: FilteredSearchSectionContents,
931 ) -> std::prelude::v1::Result<Self, Self::Error> {
932 let music_shelf_contents = value
933 .0
934 .try_into_iter()?
935 .find_path(concatcp!(MUSIC_SHELF, "/contents"))?;
936 Ok(FilteredSearchMusicShelfContents(music_shelf_contents))
937 }
938}
939impl TryFrom<FilteredSearchMusicShelfContents> for Vec<SearchResultAlbum> {
940 type Error = Error;
941 fn try_from(
942 mut value: FilteredSearchMusicShelfContents,
943 ) -> std::prelude::v1::Result<Self, Self::Error> {
944 value
946 .0
947 .try_iter_mut()?
948 .map(|a| parse_album_search_result_from_music_shelf_contents(a))
949 .collect()
950 }
951}
952impl TryFrom<FilteredSearchMusicShelfContents> for Vec<SearchResultProfile> {
953 type Error = Error;
954 fn try_from(
955 mut value: FilteredSearchMusicShelfContents,
956 ) -> std::prelude::v1::Result<Self, Self::Error> {
957 value
959 .0
960 .try_iter_mut()?
961 .map(|a| parse_profile_search_result_from_music_shelf_contents(a))
962 .collect()
963 }
964}
965impl TryFrom<FilteredSearchMusicShelfContents> for Vec<SearchResultArtist> {
966 type Error = Error;
967 fn try_from(
968 mut value: FilteredSearchMusicShelfContents,
969 ) -> std::prelude::v1::Result<Self, Self::Error> {
970 value
972 .0
973 .try_iter_mut()?
974 .map(|a| parse_artist_search_result_from_music_shelf_contents(a))
975 .collect()
976 }
977}
978impl TryFrom<FilteredSearchMusicShelfContents> for Vec<SearchResultSong> {
979 type Error = Error;
980 fn try_from(
981 mut value: FilteredSearchMusicShelfContents,
982 ) -> std::prelude::v1::Result<Self, Self::Error> {
983 value
985 .0
986 .try_iter_mut()?
987 .map(|a| parse_song_search_result_from_music_shelf_contents(a))
988 .collect()
989 }
990}
991impl TryFrom<FilteredSearchMusicShelfContents> for Vec<SearchResultVideo> {
992 type Error = Error;
993 fn try_from(
994 mut value: FilteredSearchMusicShelfContents,
995 ) -> std::prelude::v1::Result<Self, Self::Error> {
996 value
998 .0
999 .try_iter_mut()?
1000 .filter_map(|a| parse_video_search_result_from_music_shelf_contents(a).transpose())
1001 .collect()
1002 }
1003}
1004impl TryFrom<FilteredSearchMusicShelfContents> for Vec<SearchResultEpisode> {
1005 type Error = Error;
1006 fn try_from(
1007 mut value: FilteredSearchMusicShelfContents,
1008 ) -> std::prelude::v1::Result<Self, Self::Error> {
1009 value
1011 .0
1012 .try_iter_mut()?
1013 .map(|a| parse_episode_search_result_from_music_shelf_contents(a))
1014 .collect()
1015 }
1016}
1017impl TryFrom<FilteredSearchMusicShelfContents> for Vec<SearchResultPodcast> {
1018 type Error = Error;
1019 fn try_from(
1020 mut value: FilteredSearchMusicShelfContents,
1021 ) -> std::prelude::v1::Result<Self, Self::Error> {
1022 value
1024 .0
1025 .try_iter_mut()?
1026 .map(|a| parse_podcast_search_result_from_music_shelf_contents(a))
1027 .collect()
1028 }
1029}
1030impl TryFrom<FilteredSearchMusicShelfContents> for Vec<SearchResultPlaylist> {
1031 type Error = Error;
1032 fn try_from(
1033 mut value: FilteredSearchMusicShelfContents,
1034 ) -> std::prelude::v1::Result<Self, Self::Error> {
1035 value
1037 .0
1038 .try_iter_mut()?
1039 .map(|a| parse_playlist_search_result_from_music_shelf_contents(a))
1040 .collect()
1041 }
1042}
1043impl TryFrom<FilteredSearchMusicShelfContents> for Vec<SearchResultCommunityPlaylist> {
1044 type Error = Error;
1045 fn try_from(
1046 mut value: FilteredSearchMusicShelfContents,
1047 ) -> std::prelude::v1::Result<Self, Self::Error> {
1048 value
1050 .0
1051 .try_iter_mut()?
1052 .map(|a| parse_community_playlist_search_result_from_music_shelf_contents(a))
1053 .collect()
1054 }
1055}
1056impl TryFrom<FilteredSearchMusicShelfContents> for Vec<SearchResultFeaturedPlaylist> {
1057 type Error = Error;
1058 fn try_from(
1059 mut value: FilteredSearchMusicShelfContents,
1060 ) -> std::prelude::v1::Result<Self, Self::Error> {
1061 value
1063 .0
1064 .try_iter_mut()?
1065 .map(|a| parse_featured_playlist_search_result_from_music_shelf_contents(a))
1066 .collect()
1067 }
1068}
1069impl<'a, S: UnfilteredSearchType> ParseFrom<SearchQuery<'a, S>> for SearchResults {
1070 fn parse_from(p: ProcessedResult<SearchQuery<'a, S>>) -> crate::Result<Self> {
1071 let mut section_list_contents = BasicSearchSectionListContents::try_from(p)?;
1072 if section_list_contents_is_empty(&mut section_list_contents)? {
1073 return Ok(Self::default());
1074 }
1075 parse_basic_search_result_from_section_list_contents(section_list_contents)
1076 }
1077}
1078
1079impl<'a> ParseFromContinuable<SearchQuery<'a, FilteredSearch<ArtistsFilter>>>
1080 for Vec<SearchResultArtist>
1081{
1082 fn parse_from_continuable(
1083 p: ProcessedResult<SearchQuery<'a, FilteredSearch<ArtistsFilter>>>,
1084 ) -> crate::Result<(Self, Option<crate::common::ContinuationParams<'static>>)> {
1085 let mut section_contents = FilteredSearchSectionContents::try_from(p)?;
1086 if section_contents_is_empty(&mut section_contents)? {
1087 return Ok((Vec::new(), None));
1088 }
1089 let continuation_params =
1090 take_continuation_params_from_section_contents(&mut section_contents)?;
1091 let results = FilteredSearchMusicShelfContents::try_from(section_contents)?.try_into()?;
1092 Ok((results, continuation_params))
1093 }
1094 fn parse_continuation(
1095 p: ProcessedResult<
1096 GetContinuationsQuery<'_, SearchQuery<'a, FilteredSearch<ArtistsFilter>>>,
1097 >,
1098 ) -> crate::Result<(Self, Option<crate::common::ContinuationParams<'static>>)> {
1099 let crawler: JsonCrawlerOwned = p.into();
1100 let (contents, continuation_params) =
1101 get_filtered_search_continuation_music_shelf_contents_and_params(crawler)?;
1102 let results = contents.try_into()?;
1103 Ok((results, continuation_params))
1104 }
1105}
1106impl<'a> ParseFromContinuable<SearchQuery<'a, FilteredSearch<ProfilesFilter>>>
1107 for Vec<SearchResultProfile>
1108{
1109 fn parse_from_continuable(
1110 p: ProcessedResult<SearchQuery<'a, FilteredSearch<ProfilesFilter>>>,
1111 ) -> crate::Result<(Self, Option<crate::common::ContinuationParams<'static>>)> {
1112 let mut section_contents = FilteredSearchSectionContents::try_from(p)?;
1113 if section_contents_is_empty(&mut section_contents)? {
1114 return Ok((Vec::new(), None));
1115 }
1116 let continuation_params =
1117 take_continuation_params_from_section_contents(&mut section_contents)?;
1118 let results = FilteredSearchMusicShelfContents::try_from(section_contents)?.try_into()?;
1119 Ok((results, continuation_params))
1120 }
1121 fn parse_continuation(
1122 p: ProcessedResult<
1123 GetContinuationsQuery<'_, SearchQuery<'a, FilteredSearch<ProfilesFilter>>>,
1124 >,
1125 ) -> crate::Result<(Self, Option<crate::common::ContinuationParams<'static>>)> {
1126 let crawler: JsonCrawlerOwned = p.into();
1127 let (contents, continuation_params) =
1128 get_filtered_search_continuation_music_shelf_contents_and_params(crawler)?;
1129 let results = contents.try_into()?;
1130 Ok((results, continuation_params))
1131 }
1132}
1133impl<'a> ParseFromContinuable<SearchQuery<'a, FilteredSearch<AlbumsFilter>>>
1134 for Vec<SearchResultAlbum>
1135{
1136 fn parse_from_continuable(
1137 p: ProcessedResult<SearchQuery<'a, FilteredSearch<AlbumsFilter>>>,
1138 ) -> crate::Result<(Self, Option<crate::common::ContinuationParams<'static>>)> {
1139 let mut section_contents = FilteredSearchSectionContents::try_from(p)?;
1140 if section_contents_is_empty(&mut section_contents)? {
1141 return Ok((Vec::new(), None));
1142 }
1143 let continuation_params =
1144 take_continuation_params_from_section_contents(&mut section_contents)?;
1145 let results = FilteredSearchMusicShelfContents::try_from(section_contents)?.try_into()?;
1146 Ok((results, continuation_params))
1147 }
1148 fn parse_continuation(
1149 p: ProcessedResult<
1150 GetContinuationsQuery<'_, SearchQuery<'a, FilteredSearch<AlbumsFilter>>>,
1151 >,
1152 ) -> crate::Result<(Self, Option<crate::common::ContinuationParams<'static>>)> {
1153 let crawler: JsonCrawlerOwned = p.into();
1154 let (contents, continuation_params) =
1155 get_filtered_search_continuation_music_shelf_contents_and_params(crawler)?;
1156 let results = contents.try_into()?;
1157 Ok((results, continuation_params))
1158 }
1159}
1160impl<'a> ParseFromContinuable<SearchQuery<'a, FilteredSearch<SongsFilter>>>
1161 for Vec<SearchResultSong>
1162{
1163 fn parse_from_continuable(
1164 p: ProcessedResult<SearchQuery<'a, FilteredSearch<SongsFilter>>>,
1165 ) -> crate::Result<(Self, Option<crate::common::ContinuationParams<'static>>)> {
1166 let mut section_contents = FilteredSearchSectionContents::try_from(p)?;
1167 if section_contents_is_empty(&mut section_contents)? {
1168 return Ok((Vec::new(), None));
1169 }
1170 let continuation_params =
1171 take_continuation_params_from_section_contents(&mut section_contents)?;
1172 let results = FilteredSearchMusicShelfContents::try_from(section_contents)?.try_into()?;
1173 Ok((results, continuation_params))
1174 }
1175 fn parse_continuation(
1176 p: ProcessedResult<GetContinuationsQuery<'_, SearchQuery<'a, FilteredSearch<SongsFilter>>>>,
1177 ) -> crate::Result<(Self, Option<crate::common::ContinuationParams<'static>>)> {
1178 let crawler: JsonCrawlerOwned = p.into();
1179 let (contents, continuation_params) =
1180 get_filtered_search_continuation_music_shelf_contents_and_params(crawler)?;
1181 let results = contents.try_into()?;
1182 Ok((results, continuation_params))
1183 }
1184}
1185impl<'a> ParseFromContinuable<SearchQuery<'a, FilteredSearch<VideosFilter>>>
1186 for Vec<SearchResultVideo>
1187{
1188 fn parse_from_continuable(
1189 p: ProcessedResult<SearchQuery<'a, FilteredSearch<VideosFilter>>>,
1190 ) -> crate::Result<(Self, Option<crate::common::ContinuationParams<'static>>)> {
1191 let mut section_contents = FilteredSearchSectionContents::try_from(p)?;
1192 if section_contents_is_empty(&mut section_contents)? {
1193 return Ok((Vec::new(), None));
1194 }
1195 let continuation_params =
1196 take_continuation_params_from_section_contents(&mut section_contents)?;
1197 let results = FilteredSearchMusicShelfContents::try_from(section_contents)?.try_into()?;
1198 Ok((results, continuation_params))
1199 }
1200 fn parse_continuation(
1201 p: ProcessedResult<
1202 GetContinuationsQuery<'_, SearchQuery<'a, FilteredSearch<VideosFilter>>>,
1203 >,
1204 ) -> crate::Result<(Self, Option<crate::common::ContinuationParams<'static>>)> {
1205 let crawler: JsonCrawlerOwned = p.into();
1206 let (contents, continuation_params) =
1207 get_filtered_search_continuation_music_shelf_contents_and_params(crawler)?;
1208 let results = contents.try_into()?;
1209 Ok((results, continuation_params))
1210 }
1211}
1212impl<'a> ParseFromContinuable<SearchQuery<'a, FilteredSearch<EpisodesFilter>>>
1213 for Vec<SearchResultEpisode>
1214{
1215 fn parse_from_continuable(
1216 p: ProcessedResult<SearchQuery<'a, FilteredSearch<EpisodesFilter>>>,
1217 ) -> crate::Result<(Self, Option<crate::common::ContinuationParams<'static>>)> {
1218 let mut section_contents = FilteredSearchSectionContents::try_from(p)?;
1219 if section_contents_is_empty(&mut section_contents)? {
1220 return Ok((Vec::new(), None));
1221 }
1222 let continuation_params =
1223 take_continuation_params_from_section_contents(&mut section_contents)?;
1224 let results = FilteredSearchMusicShelfContents::try_from(section_contents)?.try_into()?;
1225 Ok((results, continuation_params))
1226 }
1227 fn parse_continuation(
1228 p: ProcessedResult<
1229 GetContinuationsQuery<'_, SearchQuery<'a, FilteredSearch<EpisodesFilter>>>,
1230 >,
1231 ) -> crate::Result<(Self, Option<crate::common::ContinuationParams<'static>>)> {
1232 let crawler: JsonCrawlerOwned = p.into();
1233 let (contents, continuation_params) =
1234 get_filtered_search_continuation_music_shelf_contents_and_params(crawler)?;
1235 let results = contents.try_into()?;
1236 Ok((results, continuation_params))
1237 }
1238}
1239impl<'a> ParseFromContinuable<SearchQuery<'a, FilteredSearch<PodcastsFilter>>>
1240 for Vec<SearchResultPodcast>
1241{
1242 fn parse_from_continuable(
1243 p: ProcessedResult<SearchQuery<'a, FilteredSearch<PodcastsFilter>>>,
1244 ) -> crate::Result<(Self, Option<crate::common::ContinuationParams<'static>>)> {
1245 let mut section_contents = FilteredSearchSectionContents::try_from(p)?;
1246 if section_contents_is_empty(&mut section_contents)? {
1247 return Ok((Vec::new(), None));
1248 }
1249 let continuation_params =
1250 take_continuation_params_from_section_contents(&mut section_contents)?;
1251 let results = FilteredSearchMusicShelfContents::try_from(section_contents)?.try_into()?;
1252 Ok((results, continuation_params))
1253 }
1254 fn parse_continuation(
1255 p: ProcessedResult<
1256 GetContinuationsQuery<'_, SearchQuery<'a, FilteredSearch<PodcastsFilter>>>,
1257 >,
1258 ) -> crate::Result<(Self, Option<crate::common::ContinuationParams<'static>>)> {
1259 let crawler: JsonCrawlerOwned = p.into();
1260 let (contents, continuation_params) =
1261 get_filtered_search_continuation_music_shelf_contents_and_params(crawler)?;
1262 let results = contents.try_into()?;
1263 Ok((results, continuation_params))
1264 }
1265}
1266impl<'a> ParseFromContinuable<SearchQuery<'a, FilteredSearch<CommunityPlaylistsFilter>>>
1267 for Vec<SearchResultPlaylist>
1268{
1269 fn parse_from_continuable(
1270 p: ProcessedResult<SearchQuery<'a, FilteredSearch<CommunityPlaylistsFilter>>>,
1271 ) -> crate::Result<(Self, Option<crate::common::ContinuationParams<'static>>)> {
1272 let mut section_contents = FilteredSearchSectionContents::try_from(p)?;
1273 if section_contents_is_empty(&mut section_contents)? {
1274 return Ok((Vec::new(), None));
1275 }
1276 let continuation_params =
1277 take_continuation_params_from_section_contents(&mut section_contents)?;
1278 let results = FilteredSearchMusicShelfContents::try_from(section_contents)?.try_into()?;
1279 Ok((results, continuation_params))
1280 }
1281 fn parse_continuation(
1282 p: ProcessedResult<
1283 GetContinuationsQuery<'_, SearchQuery<'a, FilteredSearch<CommunityPlaylistsFilter>>>,
1284 >,
1285 ) -> crate::Result<(Self, Option<crate::common::ContinuationParams<'static>>)> {
1286 let crawler: JsonCrawlerOwned = p.into();
1287 let (contents, continuation_params) =
1288 get_filtered_search_continuation_music_shelf_contents_and_params(crawler)?;
1289 let results = contents.try_into()?;
1290 Ok((results, continuation_params))
1291 }
1292}
1293impl<'a> ParseFromContinuable<SearchQuery<'a, FilteredSearch<FeaturedPlaylistsFilter>>>
1294 for Vec<SearchResultFeaturedPlaylist>
1295{
1296 fn parse_from_continuable(
1297 p: ProcessedResult<SearchQuery<'a, FilteredSearch<FeaturedPlaylistsFilter>>>,
1298 ) -> crate::Result<(Self, Option<crate::common::ContinuationParams<'static>>)> {
1299 let mut section_contents = FilteredSearchSectionContents::try_from(p)?;
1300 if section_contents_is_empty(&mut section_contents)? {
1301 return Ok((Vec::new(), None));
1302 }
1303 let continuation_params =
1304 take_continuation_params_from_section_contents(&mut section_contents)?;
1305 let results = FilteredSearchMusicShelfContents::try_from(section_contents)?.try_into()?;
1306 Ok((results, continuation_params))
1307 }
1308 fn parse_continuation(
1309 p: ProcessedResult<
1310 GetContinuationsQuery<'_, SearchQuery<'a, FilteredSearch<FeaturedPlaylistsFilter>>>,
1311 >,
1312 ) -> crate::Result<(Self, Option<crate::common::ContinuationParams<'static>>)> {
1313 let crawler: JsonCrawlerOwned = p.into();
1314 let (contents, continuation_params) =
1315 get_filtered_search_continuation_music_shelf_contents_and_params(crawler)?;
1316 let results = contents.try_into()?;
1317 Ok((results, continuation_params))
1318 }
1319}
1320impl<'a> ParseFromContinuable<SearchQuery<'a, FilteredSearch<PlaylistsFilter>>>
1321 for Vec<SearchResultPlaylist>
1322{
1323 fn parse_from_continuable(
1324 p: ProcessedResult<SearchQuery<'a, FilteredSearch<PlaylistsFilter>>>,
1325 ) -> crate::Result<(Self, Option<crate::common::ContinuationParams<'static>>)> {
1326 let mut section_contents = FilteredSearchSectionContents::try_from(p)?;
1327 if section_contents_is_empty(&mut section_contents)? {
1328 return Ok((Vec::new(), None));
1329 }
1330 let continuation_params =
1331 take_continuation_params_from_section_contents(&mut section_contents)?;
1332 let results = FilteredSearchMusicShelfContents::try_from(section_contents)?.try_into()?;
1333 Ok((results, continuation_params))
1334 }
1335 fn parse_continuation(
1336 p: ProcessedResult<
1337 GetContinuationsQuery<'_, SearchQuery<'a, FilteredSearch<PlaylistsFilter>>>,
1338 >,
1339 ) -> crate::Result<(Self, Option<crate::common::ContinuationParams<'static>>)> {
1340 let crawler: JsonCrawlerOwned = p.into();
1341 let (contents, continuation_params) =
1342 get_filtered_search_continuation_music_shelf_contents_and_params(crawler)?;
1343 let results = contents.try_into()?;
1344 Ok((results, continuation_params))
1345 }
1346}
1347
1348impl<'a> ParseFrom<GetSearchSuggestionsQuery<'a>> for Vec<SearchSuggestion> {
1349 fn parse_from(p: ProcessedResult<GetSearchSuggestionsQuery<'a>>) -> crate::Result<Self> {
1350 let json_crawler: JsonCrawlerOwned = p.into();
1351 let mut suggestions = json_crawler
1352 .navigate_pointer("/contents/0/searchSuggestionsSectionRenderer/contents")?;
1353 let mut results = Vec::new();
1354 for mut s in suggestions.try_iter_mut()? {
1355 let mut runs = Vec::new();
1356 if let Ok(mut search_suggestion) =
1357 s.borrow_pointer("/searchSuggestionRenderer/suggestion/runs")
1358 {
1359 for mut r in search_suggestion.try_iter_mut()? {
1360 if let Ok(true) = r.take_value_pointer("/bold") {
1361 runs.push(r.take_value_pointer("/text").map(TextRun::Bold)?)
1362 } else {
1363 runs.push(r.take_value_pointer("/text").map(TextRun::Normal)?)
1364 }
1365 }
1366 results.push(SearchSuggestion::new(SuggestionType::Prediction, runs))
1367 } else {
1368 for mut r in s
1369 .borrow_pointer("/historySuggestionRenderer/suggestion/runs")?
1370 .try_iter_mut()?
1371 {
1372 if let Ok(true) = r.take_value_pointer("/bold") {
1373 runs.push(r.take_value_pointer("/text").map(TextRun::Bold)?)
1374 } else {
1375 runs.push(r.take_value_pointer("/text").map(TextRun::Normal)?)
1376 }
1377 }
1378 results.push(SearchSuggestion::new(SuggestionType::History, runs))
1379 }
1380 }
1381 Ok(results)
1382 }
1383}