feat: add related playlists

This commit is contained in:
ThetaDev 2022-11-06 20:45:50 +01:00
parent dfd33d5e9b
commit 8af1ae303d
11 changed files with 4712 additions and 16 deletions

View file

@ -40,6 +40,7 @@ pub async fn download_testfiles(project_root: &Path) {
music_playlist(&testfiles).await;
music_playlist_cont(&testfiles).await;
music_playlist_related(&testfiles).await;
music_album(&testfiles).await;
music_search(&testfiles).await;
music_search_tracks(&testfiles).await;
@ -535,6 +536,30 @@ async fn music_playlist_cont(testfiles: &Path) {
playlist.tracks.next(&rp.query()).await.unwrap().unwrap();
}
async fn music_playlist_related(testfiles: &Path) {
let mut json_path = testfiles.to_path_buf();
json_path.push("music_playlist");
json_path.push("playlist_related.json");
if json_path.exists() {
return;
}
let rp = RustyPipe::new();
let playlist = rp
.query()
.music_playlist("PL5dDx681T4bR7ZF1IuWzOv1omlRbE7PiJ")
.await
.unwrap();
let rp = rp_testfile(&json_path);
playlist
.related_playlists
.next(&rp.query())
.await
.unwrap()
.unwrap();
}
async fn music_album(testfiles: &Path) {
for (name, id) in [
("one_artist", "MPREb_nlBWQROfvjo"),

View file

@ -63,12 +63,13 @@ impl MapResponse<MusicPlaylist> for response::MusicPlaylist {
let header = self.header.music_detail_header_renderer;
let mut content = self.contents.single_column_browse_results_renderer.contents;
let mut shelf = content
let mut music_contents = content
.try_swap_remove(0)
.ok_or(ExtractionError::InvalidData(Cow::Borrowed("no content")))?
.tab_renderer
.content
.section_list_renderer
.section_list_renderer;
let mut shelf = music_contents
.contents
.into_iter()
.find_map(|section| match section {
@ -121,6 +122,11 @@ impl MapResponse<MusicPlaylist> for response::MusicPlaylist {
None => Some(map_res.c.len() as u64),
};
let related_ctoken = music_contents
.continuations
.try_swap_remove(0)
.map(|c| c.next_continuation_data.continuation);
Ok(MapResult {
c: MusicPlaylist {
id: playlist_id,
@ -137,6 +143,13 @@ impl MapResponse<MusicPlaylist> for response::MusicPlaylist {
None,
crate::param::ContinuationEndpoint::MusicBrowse,
),
related_playlists: Paginator::new_ext(
None,
Vec::new(),
related_ctoken,
None,
crate::param::ContinuationEndpoint::MusicBrowse,
),
},
warnings: map_res.warnings,
})

View file

@ -128,12 +128,34 @@ impl MapResponse<Paginator<MusicItem>> for response::MusicContinuation {
_deobf: Option<&crate::deobfuscate::Deobfuscator>,
) -> Result<MapResult<Paginator<MusicItem>>, ExtractionError> {
let mut mapper = MusicListMapper::new(lang);
let mut shelf = self.continuation_contents.music_shelf_continuation;
mapper.map_response(shelf.contents);
let map_res = mapper.items();
let mut continuations = Vec::new();
let ctoken = shelf
.continuations
match self.continuation_contents {
response::music_item::ContinuationContents::MusicShelfContinuation(mut shelf) => {
mapper.map_response(shelf.contents);
continuations.append(&mut shelf.continuations);
}
response::music_item::ContinuationContents::SectionListContinuation(contents) => {
for c in contents.contents {
match c {
response::music_item::ItemSection::MusicShelfRenderer(mut shelf) => {
mapper.map_response(shelf.contents);
continuations.append(&mut shelf.continuations);
}
response::music_item::ItemSection::MusicCarouselShelfRenderer {
contents,
..
} => {
mapper.map_response(contents);
}
response::music_item::ItemSection::None => {}
}
}
}
}
let map_res = mapper.items();
let ctoken = continuations
.try_swap_remove(0)
.map(|cont| cont.next_continuation_data.continuation);
@ -281,7 +303,7 @@ mod tests {
use rstest::rstest;
use super::*;
use crate::model::{PlaylistItem, TrackItem};
use crate::model::{MusicPlaylistItem, PlaylistItem, TrackItem};
use crate::param::Language;
#[rstest]
@ -352,4 +374,26 @@ mod tests {
);
insta::assert_ron_snapshot!(format!("map_{}", name), paginator);
}
#[rstest]
#[case("playlist_related", "music_playlist/playlist_related")]
fn map_continuation_music_playlists(#[case] name: &str, #[case] path: &str) {
let filename = format!("testfiles/{}.json", path);
let json_path = Path::new(&filename);
let json_file = File::open(json_path).unwrap();
let items: response::MusicContinuation =
serde_json::from_reader(BufReader::new(json_file)).unwrap();
let map_res: MapResult<Paginator<MusicItem>> =
items.map_response("", Language::En, None).unwrap();
let paginator: Paginator<MusicPlaylistItem> =
map_ytm_paginator(map_res.c, None, ContinuationEndpoint::MusicBrowse);
assert!(
map_res.warnings.is_empty(),
"deserialization/mapping warnings: {:?}",
map_res.warnings
);
insta::assert_ron_snapshot!(format!("map_{}", name), paginator);
}
}

View file

@ -17,7 +17,7 @@ use crate::{
use super::{
url_endpoint::{BrowseEndpointWrap, NavigationEndpoint, PageType},
MusicContinuationData, ThumbnailsWrap,
ContentsRenderer, MusicContinuationData, ThumbnailsWrap,
};
#[serde_as]
@ -156,16 +156,15 @@ pub(crate) struct PlaylistItemData {
pub video_id: String,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MusicContentsRenderer<T> {
pub contents: Vec<T>,
/*
/// Continuation token for fetching recommended items
#[serde(default)]
#[serde_as(as = "VecSkipError<_>")]
pub continuations: Vec<MusicContinuation>,
*/
pub continuations: Vec<MusicContinuationData>,
}
#[derive(Debug, Deserialize)]
@ -204,9 +203,10 @@ pub(crate) struct MusicContinuation {
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ContinuationContents {
pub(crate) enum ContinuationContents {
#[serde(alias = "musicPlaylistShelfContinuation")]
pub music_shelf_continuation: MusicShelf,
MusicShelfContinuation(MusicShelf),
SectionListContinuation(ContentsRenderer<ItemSection>),
}
#[derive(Debug, Deserialize)]

View file

@ -2268,4 +2268,10 @@ MusicPlaylist(
ctoken: Some("4qmFsgI-EiRWTFBMNWREeDY4MVQ0YlI3WkYxSXVXek92MW9tbFJiRTdQaUoaFmVnWlFWRHBEUjFtU0FRTUl1Z1ElM0Q%3D"),
endpoint: music_browse,
),
related_playlists: Paginator(
count: None,
items: [],
ctoken: Some("4qmFsgIwEiRWTFBMNWREeDY4MVQ0YlI3WkYxSXVXek92MW9tbFJiRTdQaUoaCGtnRURDTTBH"),
endpoint: music_browse,
),
)

View file

@ -1488,4 +1488,10 @@ MusicPlaylist(
ctoken: None,
endpoint: music_browse,
),
related_playlists: Paginator(
count: None,
items: [],
ctoken: Some("4qmFsgIwEiRWTFBMMUotNkpPY2tadEVfUDlYeDhEM2IyTzZ3MGlkaHVLQmUaCGtnRURDTTBH"),
endpoint: music_browse,
),
)

View file

@ -2048,4 +2048,10 @@ MusicPlaylist(
ctoken: None,
endpoint: music_browse,
),
related_playlists: Paginator(
count: None,
items: [],
ctoken: Some("4qmFsgI5Ei1WTFJEQ0xBSzV1eV9rRlFYZG5xTWFRQ1Z4MndwVU00WmZic0dDRGliWnRrSmsaCGtnRURDTTBH"),
endpoint: music_browse,
),
)

View file

@ -0,0 +1,204 @@
---
source: src/client/pagination.rs
expression: paginator
---
Paginator(
count: Some(10),
items: [
MusicPlaylistItem(
id: "RDCLAK5uy_meKJG4fRVGUG1Z5P6O-OVRJIja9U3Ctt8",
name: "Party Schlager",
thumbnail: [
Thumbnail(
url: "https://lh3.googleusercontent.com/xrAa318NOyL334j4P3KOeuvMh2NdaC3zbcRfdP2VPQ3o_MDthD0ueeMTh-Hfk5kzmkp73nH8enOH1as=w226-h226-l90-rj",
width: 226,
height: 226,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/xrAa318NOyL334j4P3KOeuvMh2NdaC3zbcRfdP2VPQ3o_MDthD0ueeMTh-Hfk5kzmkp73nH8enOH1as=w544-h544-l90-rj",
width: 544,
height: 544,
),
],
channel: None,
track_count: None,
from_ytm: true,
),
MusicPlaylistItem(
id: "RDCLAK5uy_nCUL5fa0G5mSAxmXU9tu4uGM1SoZ44OPA",
name: "Happy German Pop",
thumbnail: [
Thumbnail(
url: "https://lh3.googleusercontent.com/vqBjVRRp93TYop30YxN84sS9ITPVBWYCpzGMBBv-1KVpoQaS0kLixwXYTMX8p84wWrMRj-IrWipg6rE=w226-h226-l90-rj",
width: 226,
height: 226,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/vqBjVRRp93TYop30YxN84sS9ITPVBWYCpzGMBBv-1KVpoQaS0kLixwXYTMX8p84wWrMRj-IrWipg6rE=w544-h544-l90-rj",
width: 544,
height: 544,
),
],
channel: None,
track_count: None,
from_ytm: true,
),
MusicPlaylistItem(
id: "RDCLAK5uy_ksgASdLdWz-NAd_rDVowAxqT6xAEN5JAA",
name: "Oktoberfest",
thumbnail: [
Thumbnail(
url: "https://lh3.googleusercontent.com/_wFTtKjF2JrQPeF4FGAJeayJD1p5I24gLf92WZc7Q6M5uOMSVouuSOB0iS7jlV6vG18kMViPjBmgW6Y=w226-h226-l90-rj",
width: 226,
height: 226,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/_wFTtKjF2JrQPeF4FGAJeayJD1p5I24gLf92WZc7Q6M5uOMSVouuSOB0iS7jlV6vG18kMViPjBmgW6Y=w544-h544-l90-rj",
width: 544,
height: 544,
),
],
channel: None,
track_count: None,
from_ytm: true,
),
MusicPlaylistItem(
id: "RDCLAK5uy_lEt1yX7jdZ0dcr4e8N5Y8pUYsaO8nUc2E",
name: "\'10s German Pop",
thumbnail: [
Thumbnail(
url: "https://lh3.googleusercontent.com/slwtKgp4BHop8sstyZ2YRVKhg-cOpcFx2vWSmBaeBP4bloW0gVe6eJcqFcO-4VkjfUTDJTdcIrzGoch6=w226-h226-l90-rj",
width: 226,
height: 226,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/slwtKgp4BHop8sstyZ2YRVKhg-cOpcFx2vWSmBaeBP4bloW0gVe6eJcqFcO-4VkjfUTDJTdcIrzGoch6=w544-h544-l90-rj",
width: 544,
height: 544,
),
],
channel: None,
track_count: None,
from_ytm: true,
),
MusicPlaylistItem(
id: "RDCLAK5uy_kn1IcNFwwz5N_qekUFnvxYzPYHHRQ9_mY",
name: "\'10s Schlager",
thumbnail: [
Thumbnail(
url: "https://lh3.googleusercontent.com/Lmxml0utaSLguhcR3Fre_60dbUfF-T87l4KouMD_nWdVEz32sn4RNOZNOLiqhBjMf0bs4LnFPSsUdJY=w226-h226-l90-rj",
width: 226,
height: 226,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/Lmxml0utaSLguhcR3Fre_60dbUfF-T87l4KouMD_nWdVEz32sn4RNOZNOLiqhBjMf0bs4LnFPSsUdJY=w544-h544-l90-rj",
width: 544,
height: 544,
),
],
channel: None,
track_count: None,
from_ytm: true,
),
MusicPlaylistItem(
id: "RDCLAK5uy_kvH_VWOl1tx0Ad4H2F_4jaTMnImcuCFXc",
name: "German Pop Throwback",
thumbnail: [
Thumbnail(
url: "https://lh3.googleusercontent.com/gCaww9YpZ-Th2I6Y7mTarqo_JJPkBerhUPTchgu06VowkRZc8ndS9Efa-AIz5uqKoGWiNf32YzkFUQ=w226-h226-l90-rj",
width: 226,
height: 226,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/gCaww9YpZ-Th2I6Y7mTarqo_JJPkBerhUPTchgu06VowkRZc8ndS9Efa-AIz5uqKoGWiNf32YzkFUQ=w544-h544-l90-rj",
width: 544,
height: 544,
),
],
channel: None,
track_count: None,
from_ytm: true,
),
MusicPlaylistItem(
id: "RDCLAK5uy_kSJ30ym-GRlGJvlZsM9xnK9dAZdonBoB0",
name: "Presenting Mark Forster",
thumbnail: [
Thumbnail(
url: "https://lh3.googleusercontent.com/ZarhsSrgS22O6JwaZl4FfgeXOtaVyq0GWZKTLCrByxYgQhrEVRj1oP4MO__moYmQuKCRB1BB67JDZfg=w226-h226-l90-rj",
width: 226,
height: 226,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/ZarhsSrgS22O6JwaZl4FfgeXOtaVyq0GWZKTLCrByxYgQhrEVRj1oP4MO__moYmQuKCRB1BB67JDZfg=w544-h544-l90-rj",
width: 544,
height: 544,
),
],
channel: None,
track_count: None,
from_ytm: true,
),
MusicPlaylistItem(
id: "PLW4r_btbY6e3p8RuogQM9F8S3dFNgsMqn",
name: "German Pop",
thumbnail: [
Thumbnail(
url: "https://yt3.ggpht.com/DN7nE9pKDZ6gU-k6TJbMmIhBf-c_CfczCJ_hACD0MMIQzCXf9h69_b8y5PaStkznCsiPBHYcuVc=s192",
width: 192,
height: 192,
),
Thumbnail(
url: "https://yt3.ggpht.com/DN7nE9pKDZ6gU-k6TJbMmIhBf-c_CfczCJ_hACD0MMIQzCXf9h69_b8y5PaStkznCsiPBHYcuVc=s576",
width: 576,
height: 576,
),
],
channel: Some(ChannelId(
id: "UCnOnrhWLv3YMoTVWqF4k82w",
name: "botevpd",
)),
track_count: Some(49),
from_ytm: false,
),
MusicPlaylistItem(
id: "RDCLAK5uy_m55iAnFa25l8bl7m8vsTsmGph3-_yVBW0",
name: "German Singalong Songs",
thumbnail: [
Thumbnail(
url: "https://lh3.googleusercontent.com/JLLNoS2hhZ54GcYoY_mD0fxVVxcZ7Ay_rT41TIbjv6n3efAUqAexZpcbK7qAS8vLw_K4NuV2R3_iRPfs=w226-h226-l90-rj",
width: 226,
height: 226,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/JLLNoS2hhZ54GcYoY_mD0fxVVxcZ7Ay_rT41TIbjv6n3efAUqAexZpcbK7qAS8vLw_K4NuV2R3_iRPfs=w544-h544-l90-rj",
width: 544,
height: 544,
),
],
channel: None,
track_count: None,
from_ytm: true,
),
MusicPlaylistItem(
id: "RDCLAK5uy_nLm6_mnMdOQD1vZypJ0yPpzJPuzfPN4d4",
name: "German Summer",
thumbnail: [
Thumbnail(
url: "https://lh3.googleusercontent.com/DKPAdAdEmbP1UYpXmQaFRIvIuZXVkRmgzxx9V_PtsvSZLBRDL8gzv9ibIJbRBcNV0toryLF4tFxEIaE=w226-h226-l90-rj",
width: 226,
height: 226,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/DKPAdAdEmbP1UYpXmQaFRIvIuZXVkRmgzxx9V_PtsvSZLBRDL8gzv9ibIJbRBcNV0toryLF4tFxEIaE=w544-h544-l90-rj",
width: 544,
height: 544,
),
],
channel: None,
track_count: None,
from_ytm: true,
),
],
ctoken: None,
endpoint: music_browse,
)

View file

@ -1014,6 +1014,8 @@ pub struct MusicPlaylist {
pub from_ytm: bool,
/// Playlist tracks
pub tracks: Paginator<TrackItem>,
/// Related playlists
pub related_playlists: Paginator<MusicPlaylistItem>,
}
/// YouTube music album object

File diff suppressed because it is too large Load diff

View file

@ -1292,8 +1292,31 @@ async fn music_playlist_cont() {
.extend_pages(&rp.query(), usize::MAX)
.await
.unwrap();
assert!(playlist.tracks.items.len() > 100);
assert!(playlist.tracks.count.unwrap() > 100);
assert_gte(playlist.tracks.items.len(), 100, "tracks");
assert_gte(playlist.tracks.count.unwrap(), 100, "track count");
}
#[tokio::test]
async fn music_playlist_related() {
let rp = RustyPipe::builder().strict().build();
let mut playlist = rp
.query()
.music_playlist("PLbZIPy20-1pN7mqjckepWF78ndb6ci_qi")
.await
.unwrap();
playlist
.related_playlists
.extend(&rp.query())
.await
.unwrap();
assert_gte(
playlist.related_playlists.items.len(),
10,
"related playlists",
);
}
#[tokio::test]