use super::{parse_item_text, ProcessedResult};
use crate::common::library::{LibraryArtist, Playlist};
use crate::common::PlaylistID;
use crate::crawler::JsonCrawler;
use crate::nav_consts::{
GRID, ITEM_SECTION, MRLIR, MTRIR, MUSIC_SHELF, NAVIGATION_BROWSE_ID, SECTION_LIST,
SECTION_LIST_ITEM, SINGLE_COLUMN_TAB, THUMBNAIL_RENDERER, TITLE, TITLE_TEXT,
};
use crate::query::{GetLibraryArtistsQuery, GetLibraryPlaylistsQuery};
use crate::{Result, Thumbnail};
use const_format::concatcp;
impl<'a> ProcessedResult<GetLibraryArtistsQuery> {
pub fn parse(self) -> Result<Vec<LibraryArtist>> {
let ProcessedResult { json_crawler, .. } = self;
parse_library_artists(json_crawler)
}
}
impl<'a> ProcessedResult<GetLibraryPlaylistsQuery> {
pub fn parse(self) -> Result<Vec<Playlist>> {
let ProcessedResult { json_crawler, .. } = self;
parse_library_playlist_query(json_crawler)
}
}
fn parse_library_artists(json_crawler: JsonCrawler) -> Result<Vec<LibraryArtist>> {
if let Some(contents) = process_library_contents_music_shelf(json_crawler) {
parse_content_list_artists(contents)
} else {
Ok(Vec::new())
}
}
fn parse_library_playlist_query(json_crawler: JsonCrawler) -> Result<Vec<Playlist>> {
if let Some(contents) = process_library_contents_grid(json_crawler) {
parse_content_list_playlist(contents)
} else {
Ok(Vec::new())
}
}
fn process_library_contents_grid(mut json_crawler: JsonCrawler) -> Option<JsonCrawler> {
let section = json_crawler.borrow_pointer(concatcp!(SINGLE_COLUMN_TAB, SECTION_LIST));
if let Ok(section) = section {
if section.path_exists("itemSectionRenderer") {
json_crawler
.navigate_pointer(concatcp!(ITEM_SECTION, GRID))
.ok()
} else {
json_crawler
.navigate_pointer(concatcp!(SINGLE_COLUMN_TAB, SECTION_LIST_ITEM, GRID))
.ok()
}
} else {
None
}
}
fn process_library_contents_music_shelf(mut json_crawler: JsonCrawler) -> Option<JsonCrawler> {
let section = json_crawler.borrow_pointer(concatcp!(SINGLE_COLUMN_TAB, SECTION_LIST));
if let Ok(section) = section {
if section.path_exists("itemSectionRenderer") {
json_crawler
.navigate_pointer(concatcp!(ITEM_SECTION, MUSIC_SHELF))
.ok()
} else {
json_crawler
.navigate_pointer(concatcp!(SINGLE_COLUMN_TAB, SECTION_LIST_ITEM, MUSIC_SHELF))
.ok()
}
} else {
None
}
}
fn parse_content_list_artists(json_crawler: JsonCrawler) -> Result<Vec<LibraryArtist>> {
let mut results = Vec::new();
for result in json_crawler
.navigate_pointer("/contents")?
.as_array_iter_mut()?
{
let mut data = result.navigate_pointer(MRLIR)?;
let channel_id = data.take_value_pointer(NAVIGATION_BROWSE_ID)?;
let artist = parse_item_text(&mut data, 0, 0)?;
let byline = parse_item_text(&mut data, 1, 0)?;
results.push(LibraryArtist {
channel_id,
artist,
byline,
})
}
Ok(results)
}
fn parse_content_list_playlist(json_crawler: JsonCrawler) -> Result<Vec<Playlist>> {
let mut results = Vec::new();
for result in json_crawler
.navigate_pointer("/items")?
.as_array_iter_mut()?
.skip(1)
.map(|c| c.navigate_pointer(MTRIR))
{
let mut result = result?;
let title = result.take_value_pointer(TITLE_TEXT)?;
let playlist_id: PlaylistID = result
.borrow_pointer(concatcp!(TITLE, NAVIGATION_BROWSE_ID))?
.take_value()?;
let thumbnails: Vec<Thumbnail> = result.take_value_pointer(THUMBNAIL_RENDERER)?;
let mut description = None;
let count = None;
let author = None;
if let Ok(mut subtitle) = result.borrow_pointer("/subtitle") {
let runs = subtitle.borrow_pointer("/runs")?.into_array_iter_mut()?;
description = Some(
runs.map(|mut c| c.take_value_pointer::<String, &str>("/text"))
.collect::<Result<String>>()?,
);
}
let playlist = Playlist {
description,
author,
playlist_id,
title,
thumbnails,
count,
};
results.push(playlist)
}
Ok(results)
}
#[cfg(test)]
mod tests {
use crate::{
common::library::{LibraryArtist, Playlist},
crawler::JsonCrawler,
parse::ProcessedResult,
process::JsonCloner,
query::{GetLibraryArtistsQuery, GetLibraryPlaylistsQuery},
};
use serde_json::json;
#[test]
fn test_library_playlists_dummy_json() {
let testfile = std::fs::read_to_string("test_json/get_library_playlists.json").unwrap();
let cloner = JsonCloner::from_string(testfile).unwrap();
let json_crawler = JsonCrawler::from_json_cloner(cloner);
let processed = ProcessedResult::from_raw(json_crawler, GetLibraryPlaylistsQuery {});
let result = processed.parse().unwrap();
let expected = json!([
{
"playlist_id": "VLLM",
"title": "Liked Music",
"thumbnails": [
{
"height": 192,
"width": 192,
"url": "https://www.gstatic.com/youtube/media/ytm/images/pbg/liked-music-@192.png"
},
{
"height": 576,
"width": 576,
"url": "https://www.gstatic.com/youtube/media/ytm/images/pbg/liked-music-@576.png"
}
],
"count": null,
"description": "Auto playlist",
"author": null
},
{
"playlist_id": "VLPLCZQcydUIP07hMOwAXIag92l76d3z3Thv",
"title": "Listen later",
"thumbnails": [
{
"height": 192,
"width": 192,
"url": "https://yt3.ggpht.com/oGdMcu3X8XKqSc9QMRqV3rqznKuPScNylHcqmKiBfLE1TZ7gkqFJRwQX2rAiWyAOuLPM614fSDo=s192"
},
{
"height": 576,
"width": 576,
"url": "https://yt3.ggpht.com/oGdMcu3X8XKqSc9QMRqV3rqznKuPScNylHcqmKiBfLE1TZ7gkqFJRwQX2rAiWyAOuLPM614fSDo=s576"
}
],
"count": null,
"description": "Nick Dowsett • 20 tracks",
"author": null
},
{
"playlist_id": "VLRDCLAK5uy_lRzD6ZcGWU_ef3r4y7ifNYLiGmCCX_jIk",
"title": "Deadly Hotlist",
"thumbnails": [
{
"height": 226,
"width": 226,
"url": "https://lh3.googleusercontent.com/HJoX79I4ngSCHXjzEWHwWpvwlK2cMhbezyKN8I-lH06APDbjIAUymVCI1VmeB5EcrNwglLAB0Edlt1KL=w226-h226-l90-rj"
},
{
"height": 544,
"width": 544,
"url": "https://lh3.googleusercontent.com/HJoX79I4ngSCHXjzEWHwWpvwlK2cMhbezyKN8I-lH06APDbjIAUymVCI1VmeB5EcrNwglLAB0Edlt1KL=w544-h544-l90-rj"
}
],
"count": null,
"description": "YouTube Music • 50 songs",
"author": null
},
{
"playlist_id": "VLSE",
"title": "Episodes for Later",
"thumbnails": [
{
"height": 192,
"width": 192,
"url": "https://www.gstatic.com/youtube/media/ytm/images/pbg/saved-episodes-@192.png"
},
{
"height": 576,
"width": 576,
"url": "https://www.gstatic.com/youtube/media/ytm/images/pbg/saved-episodes-@576.png"
}
],
"count": null,
"description": "Episodes you save for later",
"author": null
}
]);
let expected: Vec<Playlist> = serde_json::from_value(expected).unwrap();
assert_eq!(result, expected);
}
#[test]
fn test_library_artists_dummy_json() {
let testfile = std::fs::read_to_string("test_json/get_library_artists.json").unwrap();
let cloner = JsonCloner::from_string(testfile).unwrap();
let json_crawler = JsonCrawler::from_json_cloner(cloner);
let processed = ProcessedResult::from_raw(json_crawler, GetLibraryArtistsQuery::default());
let result = processed.parse().unwrap();
let expected = json!(
[
{
"channel_id" : "MPLAUCprAFmT0C6O4X0ToEXpeFTQ",
"artist": "Kendrick Lamar",
"byline": "16 songs"
},
{
"channel_id" : "MPLAUC_yH_GaGHZk9ewo5ghQA75w",
"artist": "Dream Theater",
"byline": "1 song"
},
{
"channel_id" : "MPLAUCHUlZT-VoVWIID4xcJZ5s6g",
"artist": "Nils Frahm",
"byline": "1 song"
},
]
);
let expected: Vec<LibraryArtist> = serde_json::from_value(expected).unwrap();
assert_eq!(result, expected);
}
}