1use super::{
2 BADGE_LABEL, CONTINUATION_PARAMS, GRID_CONTINUATION, MENU_LIKE_STATUS,
3 MUSIC_SHELF_CONTINUATION, ParseFrom, ParsedPodcastChannel, ProcessedResult, SUBTITLE,
4 SUBTITLE_BADGE_LABEL, SUBTITLE2, SUBTITLE3, SearchResultAlbum, THUMBNAILS, TableListSong,
5 fixed_column_item_pointer, parse_flex_column_item, parse_library_management_items_from_menu,
6 parse_podcast_channel,
7};
8use crate::Result;
9use crate::common::{
10 ApiOutcome, ArtistChannelID, ContinuationParams, Explicit, PlaylistID, PodcastChannelID,
11 PodcastID, Thumbnail,
12};
13use crate::continuations::ParseFromContinuable;
14use crate::nav_consts::{
15 GRID, ITEM_SECTION, MENU_ITEMS, MRLIR, MTRIR, MUSIC_SHELF, NAVIGATION_BROWSE_ID,
16 NAVIGATION_PLAYLIST_ID, PLAY_BUTTON, SECTION_LIST, SECTION_LIST_ITEM, SINGLE_COLUMN_TAB,
17 SUBTITLE_BADGE_ICON, THUMBNAIL_RENDERER, TITLE, TITLE_TEXT, WATCH_VIDEO_ID,
18};
19use crate::query::library::{GetLibraryChannelsQuery, GetLibraryPodcastsQuery};
20use crate::query::{
21 EditSongLibraryStatusQuery, GetContinuationsQuery, GetLibraryAlbumsQuery,
22 GetLibraryArtistSubscriptionsQuery, GetLibraryArtistsQuery, GetLibraryPlaylistsQuery,
23 GetLibrarySongsQuery,
24};
25use crate::youtube_enums::YoutubeMusicBadgeRendererIcon;
26use const_format::concatcp;
27use json_crawler::{CrawlerResult, JsonCrawler, JsonCrawlerBorrowed, JsonCrawlerOwned};
28use serde::{Deserialize, Serialize};
29
30#[derive(PartialEq, Debug, Clone, Deserialize, Serialize)]
31#[non_exhaustive]
32pub struct GetLibraryArtistSubscription {
34 pub name: String,
35 pub subscribers: String,
36 pub channel_id: ArtistChannelID<'static>,
37 pub thumbnails: Vec<Thumbnail>,
38}
39
40#[derive(PartialEq, Debug, Clone, Deserialize, Serialize)]
41#[non_exhaustive]
42pub struct LibraryArtistSubscription {
44 pub name: String,
45 pub subscribers: String,
46 pub channel_id: ArtistChannelID<'static>,
47 pub thumbnails: Vec<Thumbnail>,
48}
49
50#[derive(PartialEq, Debug, Clone, Deserialize, Serialize)]
51#[non_exhaustive]
52pub struct LibraryPlaylist {
53 pub playlist_id: PlaylistID<'static>,
54 pub title: String,
55 pub thumbnails: Vec<Thumbnail>,
56 pub count: Option<usize>,
57 pub description: Option<String>,
58 pub author: Option<String>,
59}
60#[derive(PartialEq, Debug, Clone, Deserialize, Serialize)]
61#[non_exhaustive]
62pub struct LibraryArtist {
63 pub channel_id: ArtistChannelID<'static>,
64 pub artist: String,
65 pub byline: String, }
67#[derive(PartialEq, Debug, Clone, Deserialize, Serialize)]
68#[non_exhaustive]
69pub struct LibraryPodcast {
70 pub title: String,
71 pub channels: Vec<ParsedPodcastChannel>,
72 pub podcast_id: PodcastID<'static>,
73 pub thumbnails: Vec<Thumbnail>,
74 pub podcast_source: PodcastSource,
75}
76#[derive(PartialEq, Debug, Clone, Deserialize, Serialize)]
77#[non_exhaustive]
78pub struct LibraryChannel {
79 pub title: String,
80 pub subscribers: String,
81 pub channel_id: PodcastChannelID<'static>,
82 pub thumbnails: Vec<Thumbnail>,
83}
84
85#[derive(PartialEq, Debug, Clone, Deserialize, Serialize)]
86pub enum PodcastSource {
87 Rss,
88 YouTube,
89}
90
91impl ParseFromContinuable<GetLibraryArtistSubscriptionsQuery> for Vec<LibraryArtistSubscription> {
92 fn parse_from_continuable(
93 p: ProcessedResult<GetLibraryArtistSubscriptionsQuery>,
94 ) -> crate::Result<(Self, Option<ContinuationParams<'static>>)> {
95 let json_crawler: JsonCrawlerOwned = p.into();
96 let music_shelf = json_crawler.navigate_pointer(concatcp!(
97 SINGLE_COLUMN_TAB,
98 SECTION_LIST_ITEM,
99 MUSIC_SHELF,
100 ))?;
101 parse_library_artist_subscriptions(music_shelf)
102 }
103 fn parse_continuation(
104 p: ProcessedResult<GetContinuationsQuery<'_, GetLibraryArtistSubscriptionsQuery>>,
105 ) -> crate::Result<(Self, Option<ContinuationParams<'static>>)> {
106 let json_crawler: JsonCrawlerOwned = p.into();
107 let music_shelf = json_crawler.navigate_pointer(MUSIC_SHELF_CONTINUATION)?;
108 parse_library_artist_subscriptions(music_shelf)
109 }
110}
111
112impl ParseFromContinuable<GetLibraryAlbumsQuery> for Vec<SearchResultAlbum> {
113 fn parse_from_continuable(
114 p: ProcessedResult<GetLibraryAlbumsQuery>,
115 ) -> crate::Result<(Self, Option<ContinuationParams<'static>>)> {
116 let json_crawler: JsonCrawlerOwned = p.into();
117 let grid_renderer =
118 json_crawler.navigate_pointer(concatcp!(SINGLE_COLUMN_TAB, SECTION_LIST_ITEM, GRID))?;
119 parse_library_albums(grid_renderer)
120 }
121 fn parse_continuation(
122 p: ProcessedResult<GetContinuationsQuery<'_, GetLibraryAlbumsQuery>>,
123 ) -> crate::Result<(Self, Option<ContinuationParams<'static>>)> {
124 let json_crawler: JsonCrawlerOwned = p.into();
125 let grid_items = json_crawler.navigate_pointer(GRID_CONTINUATION)?;
126 parse_library_albums(grid_items)
127 }
128}
129
130impl ParseFromContinuable<GetLibrarySongsQuery> for Vec<TableListSong> {
131 fn parse_from_continuable(
132 p: ProcessedResult<GetLibrarySongsQuery>,
133 ) -> crate::Result<(Self, Option<ContinuationParams<'static>>)> {
134 let json_crawler: JsonCrawlerOwned = p.into();
135 let music_shelf = json_crawler.navigate_pointer(concatcp!(
136 SINGLE_COLUMN_TAB,
137 SECTION_LIST_ITEM,
138 MUSIC_SHELF,
139 ))?;
140 parse_library_songs(music_shelf)
141 }
142 fn parse_continuation(
143 p: ProcessedResult<GetContinuationsQuery<'_, GetLibrarySongsQuery>>,
144 ) -> crate::Result<(Self, Option<ContinuationParams<'static>>)> {
145 let json_crawler: JsonCrawlerOwned = p.into();
146 let music_shelf = json_crawler.navigate_pointer(MUSIC_SHELF_CONTINUATION)?;
147 parse_library_songs(music_shelf)
148 }
149}
150
151impl ParseFromContinuable<GetLibraryArtistsQuery> for Vec<LibraryArtist> {
152 fn parse_from_continuable(
153 p: ProcessedResult<GetLibraryArtistsQuery>,
154 ) -> crate::Result<(Self, Option<ContinuationParams<'static>>)> {
155 let json_crawler = p.into();
156 let maybe_music_shelf = process_library_contents_music_shelf(json_crawler);
157 if let Some(music_shelf) = maybe_music_shelf {
158 parse_content_list_artists(music_shelf)
159 } else {
160 Ok((Vec::new(), None))
161 }
162 }
163 fn parse_continuation(
164 p: ProcessedResult<GetContinuationsQuery<'_, GetLibraryArtistsQuery>>,
165 ) -> crate::Result<(Self, Option<ContinuationParams<'static>>)> {
166 let json_crawler = JsonCrawlerOwned::from(p);
167 let music_shelf = json_crawler.navigate_pointer(MUSIC_SHELF_CONTINUATION)?;
168 parse_content_list_artists(music_shelf)
169 }
170}
171
172impl ParseFromContinuable<GetLibraryPlaylistsQuery> for Vec<LibraryPlaylist> {
173 fn parse_from_continuable(
174 p: ProcessedResult<GetLibraryPlaylistsQuery>,
175 ) -> crate::Result<(Self, Option<ContinuationParams<'static>>)> {
176 let json_crawler = p.into();
178 let maybe_grid_renderer = process_library_contents_grid(json_crawler);
179 if let Some(grid_renderer) = maybe_grid_renderer {
180 parse_library_playlists(grid_renderer)
181 } else {
182 Ok((vec![], None))
183 }
184 }
185 fn parse_continuation(
186 p: ProcessedResult<GetContinuationsQuery<'_, GetLibraryPlaylistsQuery>>,
187 ) -> crate::Result<(Self, Option<ContinuationParams<'static>>)> {
188 let json_crawler: JsonCrawlerOwned = p.into();
189 let grid_renderer = json_crawler.navigate_pointer(GRID_CONTINUATION)?;
190 parse_library_playlists(grid_renderer)
191 }
192}
193
194impl ParseFromContinuable<GetLibraryPodcastsQuery> for Vec<LibraryPodcast> {
195 fn parse_from_continuable(
196 p: ProcessedResult<GetLibraryPodcastsQuery>,
197 ) -> crate::Result<(Self, Option<ContinuationParams<'static>>)> {
198 let json_crawler: JsonCrawlerOwned = p.into();
199 let maybe_grid_renderer = process_library_contents_grid(json_crawler);
200 if let Some(grid_renderer) = maybe_grid_renderer {
201 parse_library_podcasts(grid_renderer)
202 } else {
203 Ok((vec![], None))
204 }
205 }
206 fn parse_continuation(
207 p: ProcessedResult<GetContinuationsQuery<'_, GetLibraryPodcastsQuery>>,
208 ) -> crate::Result<(Self, Option<ContinuationParams<'static>>)> {
209 let json_crawler: JsonCrawlerOwned = p.into();
210 let grid_renderer = json_crawler.navigate_pointer(GRID_CONTINUATION)?;
211 parse_library_podcasts(grid_renderer)
212 }
213}
214
215impl ParseFromContinuable<GetLibraryChannelsQuery> for Vec<LibraryChannel> {
216 fn parse_from_continuable(
217 p: ProcessedResult<GetLibraryChannelsQuery>,
218 ) -> crate::Result<(Self, Option<ContinuationParams<'static>>)> {
219 let json_crawler = p.into();
220 let maybe_music_shelf = process_library_contents_music_shelf(json_crawler);
221 if let Some(music_shelf) = maybe_music_shelf {
222 parse_content_list_channels(music_shelf)
223 } else {
224 Ok((Vec::new(), None))
225 }
226 }
227 fn parse_continuation(
228 p: ProcessedResult<GetContinuationsQuery<'_, GetLibraryChannelsQuery>>,
229 ) -> crate::Result<(Self, Option<ContinuationParams<'static>>)> {
230 let json_crawler = JsonCrawlerOwned::from(p);
231 let music_shelf = json_crawler.navigate_pointer(MUSIC_SHELF_CONTINUATION)?;
232 parse_content_list_channels(music_shelf)
233 }
234}
235
236impl ParseFrom<EditSongLibraryStatusQuery<'_>> for Vec<ApiOutcome> {
237 fn parse_from(p: super::ProcessedResult<EditSongLibraryStatusQuery>) -> Result<Self> {
238 let json_crawler = JsonCrawlerOwned::from(p);
239 json_crawler
240 .navigate_pointer("/feedbackResponses")?
241 .try_into_iter()?
242 .map(|mut response| {
243 response
244 .take_value_pointer::<bool>("/isProcessed")
245 .map(|p| {
246 if p {
247 return ApiOutcome::Success;
248 }
249 ApiOutcome::Failure
250 })
251 })
252 .rev()
253 .collect::<CrawlerResult<_>>()
254 .map_err(Into::into)
255 }
256}
257
258fn parse_library_albums(
259 mut grid_renderer: JsonCrawlerOwned,
260) -> Result<(Vec<SearchResultAlbum>, Option<ContinuationParams<'static>>)> {
261 let continuation_params = grid_renderer.take_value_pointer(CONTINUATION_PARAMS).ok();
262 let albums = grid_renderer
263 .navigate_pointer("/items")?
264 .try_into_iter()?
265 .map(parse_item_list_album)
266 .collect::<Result<_>>()?;
267 Ok((albums, continuation_params))
268}
269fn parse_library_songs(
270 mut music_shelf: JsonCrawlerOwned,
271) -> Result<(Vec<TableListSong>, Option<ContinuationParams<'static>>)> {
272 let continuation_params = music_shelf.take_value_pointer(CONTINUATION_PARAMS).ok();
273 let songs = music_shelf
274 .navigate_pointer("/contents")?
275 .try_into_iter()?
276 .map(|mut item| {
277 let Ok(mut data) = item.borrow_pointer(MRLIR) else {
278 return Ok(None);
279 };
280 let title = super::parse_flex_column_item(&mut data, 0, 0)?;
281 if title == "Shuffle all" {
282 return Ok(None);
283 }
284 Ok(Some(parse_table_list_song(title, data)?))
285 })
286 .filter_map(Result::transpose)
287 .collect::<Result<_>>()?;
288 Ok((songs, continuation_params))
289}
290fn parse_library_artist_subscriptions(
291 mut music_shelf: JsonCrawlerOwned,
292) -> Result<(
293 Vec<LibraryArtistSubscription>,
294 Option<ContinuationParams<'static>>,
295)> {
296 let continuation_params = music_shelf.take_value_pointer(CONTINUATION_PARAMS).ok();
297 let subscriptions = music_shelf
298 .navigate_pointer("/contents")?
299 .try_into_iter()?
300 .map(parse_content_list_artist_subscription)
301 .collect::<Result<_>>()?;
302 Ok((subscriptions, continuation_params))
303}
304
305fn parse_library_playlists(
306 mut grid_renderer: JsonCrawlerOwned,
307) -> Result<(Vec<LibraryPlaylist>, Option<ContinuationParams<'static>>)> {
308 let continuation_params = grid_renderer.take_value_pointer(CONTINUATION_PARAMS).ok();
309 let playlists = grid_renderer
310 .navigate_pointer("/items")?
311 .try_into_iter()?
312 .skip(1)
314 .map(parse_content_list_playlist)
315 .collect::<Result<_>>()?;
316 Ok((playlists, continuation_params))
317}
318fn parse_library_podcasts(
319 mut grid_renderer: impl JsonCrawler,
320) -> Result<(Vec<LibraryPodcast>, Option<ContinuationParams<'static>>)> {
321 let continuation_params = grid_renderer.take_value_pointer(CONTINUATION_PARAMS).ok();
322 let res = grid_renderer
323 .navigate_pointer("/items")?
324 .try_into_iter()?
325 .skip(1)
327 .filter_map(|item| parse_content_list_podcast(item).transpose())
328 .collect::<Result<_>>()?;
329 Ok((res, continuation_params))
330}
331
332fn process_library_contents_grid(mut json_crawler: JsonCrawlerOwned) -> Option<JsonCrawlerOwned> {
335 let section = json_crawler.borrow_pointer(concatcp!(SINGLE_COLUMN_TAB, SECTION_LIST));
336 if let Ok(section) = section {
338 if section.path_exists("/itemSectionRenderer") {
339 json_crawler
340 .navigate_pointer(concatcp!(ITEM_SECTION, GRID))
341 .ok()
342 } else {
343 json_crawler
344 .navigate_pointer(concatcp!(SINGLE_COLUMN_TAB, SECTION_LIST_ITEM, GRID))
345 .ok()
346 }
347 } else {
348 None
349 }
350}
351fn process_library_contents_music_shelf(
354 mut json_crawler: JsonCrawlerOwned,
355) -> Option<JsonCrawlerOwned> {
356 let section = json_crawler.borrow_pointer(concatcp!(SINGLE_COLUMN_TAB, SECTION_LIST));
357 if let Ok(section) = section {
359 if section.path_exists("itemSectionRenderer") {
360 json_crawler
361 .navigate_pointer(concatcp!(ITEM_SECTION, MUSIC_SHELF))
362 .ok()
363 } else {
364 json_crawler
365 .navigate_pointer(concatcp!(SINGLE_COLUMN_TAB, SECTION_LIST_ITEM, MUSIC_SHELF))
366 .ok()
367 }
368 } else {
369 None
370 }
371}
372
373fn parse_item_list_album(mut json_crawler: JsonCrawlerOwned) -> Result<SearchResultAlbum> {
374 let mut data = json_crawler.borrow_pointer("/musicTwoRowItemRenderer")?;
375 let browse_id = data.take_value_pointer(NAVIGATION_BROWSE_ID)?;
376 let thumbnails = data.take_value_pointer(THUMBNAIL_RENDERER)?;
377 let title = data.take_value_pointer(TITLE_TEXT)?;
378 let artist = data.take_value_pointer(SUBTITLE2)?;
379 let year = data.take_value_pointer(SUBTITLE3)?;
380 let album_type = data.take_value_pointer(SUBTITLE)?;
381 let explicit = if data.path_exists(SUBTITLE_BADGE_LABEL) {
382 Explicit::IsExplicit
383 } else {
384 Explicit::NotExplicit
385 };
386 Ok(SearchResultAlbum {
387 title,
388 artist,
389 year,
390 explicit,
391 album_id: browse_id,
392 album_type,
393 thumbnails,
394 })
395}
396
397fn parse_content_list_artist_subscription(
398 mut json_crawler: JsonCrawlerOwned,
399) -> Result<LibraryArtistSubscription> {
400 let mut data = json_crawler.borrow_pointer(MRLIR)?;
401 let channel_id = data.take_value_pointer(NAVIGATION_BROWSE_ID)?;
402 let name = parse_flex_column_item(&mut data, 0, 0)?;
403 let subscribers = parse_flex_column_item(&mut data, 1, 0)?;
404 let thumbnails = data.take_value_pointer(THUMBNAILS)?;
405 Ok(LibraryArtistSubscription {
406 name,
407 subscribers,
408 channel_id,
409 thumbnails,
410 })
411}
412
413fn parse_content_list_artists(
414 mut json_crawler: JsonCrawlerOwned,
415) -> Result<(Vec<LibraryArtist>, Option<ContinuationParams<'static>>)> {
416 let continuation_params = json_crawler.take_value_pointer(CONTINUATION_PARAMS).ok();
417 let artists = json_crawler
418 .navigate_pointer("/contents")?
419 .try_iter_mut()?
420 .map(|item| {
421 let mut data = item.navigate_pointer(MRLIR)?;
422 let channel_id = data.take_value_pointer(NAVIGATION_BROWSE_ID)?;
423 let artist = parse_flex_column_item(&mut data, 0, 0)?;
424 let byline = parse_flex_column_item(&mut data, 1, 0)?;
425 Ok(LibraryArtist {
426 channel_id,
427 artist,
428 byline,
429 })
430 })
431 .collect::<Result<_>>()?;
432 Ok((artists, continuation_params))
433}
434
435fn parse_content_list_channels(
436 mut json_crawler: JsonCrawlerOwned,
437) -> Result<(Vec<LibraryChannel>, Option<ContinuationParams<'static>>)> {
438 let continuation_params = json_crawler.take_value_pointer(CONTINUATION_PARAMS).ok();
439 let artists = json_crawler
440 .navigate_pointer("/contents")?
441 .try_iter_mut()?
442 .map(|item| {
443 let mut data = item.navigate_pointer(MRLIR)?;
444 let channel_id = data.take_value_pointer(NAVIGATION_BROWSE_ID)?;
445 let title = parse_flex_column_item(&mut data, 0, 0)?;
446 let subscribers = parse_flex_column_item(&mut data, 1, 0)?;
447 let thumbnails = data.take_value_pointer(THUMBNAILS)?;
448 Ok(LibraryChannel {
449 title,
450 subscribers,
451 channel_id,
452 thumbnails,
453 })
454 })
455 .collect::<Result<_>>()?;
456 Ok((artists, continuation_params))
457}
458
459fn parse_table_list_song(title: String, mut data: JsonCrawlerBorrowed) -> Result<TableListSong> {
460 let video_id = data.take_value_pointer(concatcp!(
461 PLAY_BUTTON,
462 "/playNavigationEndpoint",
463 WATCH_VIDEO_ID
464 ))?;
465 let library_management =
466 parse_library_management_items_from_menu(data.borrow_pointer(MENU_ITEMS)?)?;
467 let like_status = data.take_value_pointer(MENU_LIKE_STATUS)?;
468 let artists = super::parse_song_artists(&mut data, 1)?;
469 let album = super::parse_song_album(&mut data, 2)?;
470 let duration = data
471 .borrow_pointer(fixed_column_item_pointer(0))?
472 .take_value_pointers(&["/text/simpleText", "/text/runs/0/text"])?;
473 let thumbnails = data.take_value_pointer(THUMBNAILS)?;
474 let is_available = data
475 .take_value_pointer::<String>("/musicItemRendererDisplayPolicy")
476 .map(|m| m != "MUSIC_ITEM_RENDERER_DISPLAY_POLICY_GREY_OUT")
477 .unwrap_or(true);
478
479 let explicit = if data.path_exists(BADGE_LABEL) {
480 Explicit::IsExplicit
481 } else {
482 Explicit::NotExplicit
483 };
484 let playlist_id = data.take_value_pointer(concatcp!(
485 MENU_ITEMS,
486 "/0/menuNavigationItemRenderer",
487 NAVIGATION_PLAYLIST_ID
488 ))?;
489 Ok(TableListSong {
490 video_id,
491 duration,
492 library_management,
493 title,
494 artists,
495 like_status,
496 thumbnails,
497 explicit,
498 album,
499 playlist_id,
500 is_available,
501 })
502}
503
504fn parse_content_list_playlist(item: JsonCrawlerOwned) -> Result<LibraryPlaylist> {
505 let mut mtrir = item.navigate_pointer(MTRIR)?;
507 let title = mtrir.take_value_pointer(TITLE_TEXT)?;
508 let playlist_id: PlaylistID = mtrir
509 .borrow_pointer(concatcp!(TITLE, NAVIGATION_BROWSE_ID))?
510 .take_value()?;
513 let thumbnails: Vec<Thumbnail> = mtrir.take_value_pointer(THUMBNAIL_RENDERER)?;
514 let mut description = None;
515 let count = None;
516 let author = None;
517 if let Ok(mut subtitle) = mtrir.borrow_pointer("/subtitle") {
518 let runs = subtitle.borrow_pointer("/runs")?.try_into_iter()?;
519 description = Some(
522 runs.map(|mut c| c.take_value_pointer::<String>("/text"))
523 .collect::<std::result::Result<String, _>>()?,
524 );
525 }
526 Ok(LibraryPlaylist {
527 description,
528 author,
529 playlist_id,
530 title,
531 thumbnails,
532 count,
533 })
534}
535
536fn parse_content_list_podcast(item: impl JsonCrawler) -> Result<Option<LibraryPodcast>> {
537 let mut mtrir = item.navigate_pointer(MTRIR)?;
538 let title = mtrir.take_value_pointer(TITLE_TEXT)?;
539 if title == "New Episodes" || title == "Episodes for Later" {
543 return Ok(None);
544 }
545 let podcast_id: PodcastID = mtrir
546 .borrow_pointer(concatcp!(TITLE, NAVIGATION_BROWSE_ID))?
547 .take_value()?;
550 let thumbnails: Vec<Thumbnail> = mtrir.take_value_pointer(THUMBNAIL_RENDERER)?;
551 let maybe_badge_icon = mtrir
552 .take_value_pointer::<YoutubeMusicBadgeRendererIcon>(SUBTITLE_BADGE_ICON)
553 .ok();
554 let podcast_source = match maybe_badge_icon {
555 Some(YoutubeMusicBadgeRendererIcon::Rss) => PodcastSource::Rss,
556 _ => PodcastSource::YouTube,
557 };
558 let channels = mtrir
559 .navigate_pointer("/subtitle/runs")?
560 .try_into_iter()?
561 .map(parse_podcast_channel)
562 .collect::<Result<Vec<_>>>()?;
563 Ok(Some(LibraryPodcast {
564 title,
565 thumbnails,
566 channels,
567 podcast_id,
568 podcast_source,
569 }))
570}
571
572#[cfg(test)]
573mod tests {
574 use crate::auth::BrowserToken;
575
576 #[tokio::test]
577 async fn test_library_playlists_dummy_json() {
578 parse_with_matching_continuation_test!(
579 "./test_json/get_library_playlists.json",
580 "./test_json/get_library_playlists_continuation_mock.json",
581 "./test_json/get_library_playlists_output.txt",
582 crate::query::GetLibraryPlaylistsQuery,
583 BrowserToken
584 );
585 }
586 #[tokio::test]
587 async fn test_get_library_artists_dummy_json() {
588 parse_with_matching_continuation_test!(
589 "./test_json/get_library_artists.json",
590 "./test_json/get_library_artists_continuation_mock.json",
591 "./test_json/get_library_artists_output.txt",
592 crate::query::GetLibraryArtistsQuery::default(),
593 BrowserToken
594 );
595 }
596 #[tokio::test]
597 async fn test_get_library_albums() {
598 parse_with_matching_continuation_test!(
599 "./test_json/get_library_albums_20240701.json",
600 "./test_json/get_library_albums_continuation_mock.json",
601 "./test_json/get_library_albums_20240701_output.txt",
602 crate::query::GetLibraryAlbumsQuery::default(),
603 BrowserToken
604 );
605 }
606 #[tokio::test]
607 async fn test_get_library_songs() {
608 parse_test!(
609 "./test_json/get_library_songs_20240701.json",
610 "./test_json/get_library_songs_20240701_output.txt",
611 crate::query::GetLibrarySongsQuery::default(),
612 BrowserToken
613 );
614 }
615 #[tokio::test]
616 async fn test_get_library_songs_continuation() {
617 parse_continuations_test!(
618 "./test_json/get_library_songs_continuation_20240910.json",
619 "./test_json/get_library_songs_continuation_20240910_output.txt",
620 crate::query::GetLibrarySongsQuery::default(),
621 BrowserToken
622 );
623 }
624 #[tokio::test]
625 async fn test_get_library_artist_subscriptions() {
626 parse_with_matching_continuation_test!(
627 "./test_json/get_library_artist_subscriptions_20240701.json",
628 "./test_json/get_library_artist_subscriptions_continuation_mock.json",
629 "./test_json/get_library_artist_subscriptions_20240701_output.txt",
630 crate::query::GetLibraryArtistSubscriptionsQuery::default(),
631 BrowserToken
632 );
633 }
634 #[tokio::test]
635 async fn test_get_library_podcasts() {
636 parse_with_matching_continuation_test!(
637 "./test_json/get_library_podcasts_20250626.json",
638 "./test_json/get_library_podcasts_continuation_20250626.json",
639 "./test_json/get_library_podcasts_20250626_output.txt",
640 crate::query::GetLibraryPodcastsQuery::default(),
641 BrowserToken
642 );
643 }
644 #[tokio::test]
645 async fn test_get_library_channels() {
646 parse_with_matching_continuation_test!(
647 "./test_json/get_library_channels_20250626.json",
648 "./test_json/get_library_channels_continuation_20250626.json",
649 "./test_json/get_library_channels_20250626_output.txt",
650 crate::query::GetLibraryChannelsQuery::default(),
651 BrowserToken
652 );
653 }
654 #[tokio::test]
655 async fn test_edit_song_library_status() {
656 parse_test!(
658 "./test_json/remove_history_items_20240704.json",
659 "./test_json/remove_history_items_20240704_output.txt",
660 crate::query::EditSongLibraryStatusQuery::new_from_add_to_library_feedback_tokens(
661 Vec::new()
662 )
663 .with_remove_from_library_feedback_tokens(vec![]),
664 BrowserToken
665 );
666 }
667}