fix: a/b test 20: music continuation item renderer
This commit is contained in:
parent
8d4d180f35
commit
5bf3576c7e
8 changed files with 88 additions and 21 deletions
|
|
@ -39,10 +39,14 @@ pub enum ABTest {
|
||||||
ChannelPlaylistsLockup = 17,
|
ChannelPlaylistsLockup = 17,
|
||||||
MusicPlaylistFacepile = 18,
|
MusicPlaylistFacepile = 18,
|
||||||
MusicAlbumGroupsReordered = 19,
|
MusicAlbumGroupsReordered = 19,
|
||||||
|
MusicContinuationItemRenderer = 20,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List of active A/B tests that are run when none is manually specified
|
/// List of active A/B tests that are run when none is manually specified
|
||||||
const TESTS_TO_RUN: &[ABTest] = &[ABTest::MusicAlbumGroupsReordered];
|
const TESTS_TO_RUN: &[ABTest] = &[
|
||||||
|
ABTest::MusicAlbumGroupsReordered,
|
||||||
|
ABTest::MusicContinuationItemRenderer,
|
||||||
|
];
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct ABTestRes {
|
pub struct ABTestRes {
|
||||||
|
|
@ -114,6 +118,9 @@ pub async fn run_test(
|
||||||
ABTest::ChannelPlaylistsLockup => channel_playlists_lockup(&query).await,
|
ABTest::ChannelPlaylistsLockup => channel_playlists_lockup(&query).await,
|
||||||
ABTest::MusicPlaylistFacepile => music_playlist_facepile(&query).await,
|
ABTest::MusicPlaylistFacepile => music_playlist_facepile(&query).await,
|
||||||
ABTest::MusicAlbumGroupsReordered => music_album_groups_reordered(&query).await,
|
ABTest::MusicAlbumGroupsReordered => music_album_groups_reordered(&query).await,
|
||||||
|
ABTest::MusicContinuationItemRenderer => {
|
||||||
|
music_continuation_item_renderer(&query).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.unwrap();
|
.unwrap();
|
||||||
pb.inc(1);
|
pb.inc(1);
|
||||||
|
|
@ -421,3 +428,18 @@ pub async fn music_album_groups_reordered(rp: &RustyPipeQuery) -> Result<bool> {
|
||||||
.await?;
|
.await?;
|
||||||
Ok(res.contains("\"Singles & EPs\""))
|
Ok(res.contains("\"Singles & EPs\""))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn music_continuation_item_renderer(rp: &RustyPipeQuery) -> Result<bool> {
|
||||||
|
let id = "VLPLbZIPy20-1pN7mqjckepWF78ndb6ci_qi";
|
||||||
|
let res = rp
|
||||||
|
.raw(
|
||||||
|
ClientType::DesktopMusic,
|
||||||
|
"browse",
|
||||||
|
&QBrowse {
|
||||||
|
browse_id: id,
|
||||||
|
params: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(res.contains("\"continuationItemRenderer\""))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1034,5 +1034,21 @@ commandContext missing).
|
||||||
|
|
||||||
YouTube Music used to group artist albums into 2 rows: "Albums" and "Singles".
|
YouTube Music used to group artist albums into 2 rows: "Albums" and "Singles".
|
||||||
|
|
||||||
These groups were changed into "Albums" and "Singles & EPs". Now the "Album" label is omitted
|
These groups were changed into "Albums" and "Singles & EPs". Now the "Album" label is
|
||||||
for albums in their group, while singles and EPs have a label with their type.
|
omitted for albums in their group, while singles and EPs have a label with their type.
|
||||||
|
|
||||||
|
## [20] Music continuation item renderer
|
||||||
|
|
||||||
|
- **Encountered on:** 25.01.2025
|
||||||
|
- **Impact:** 🟢 Low
|
||||||
|
- **Endpoint:** browse (YTM)
|
||||||
|
- **Status:** Common (4%)
|
||||||
|
|
||||||
|
YouTube Music now uses a `continuationItemRenderer` for music playlists instead of
|
||||||
|
putting the continuations in a separate attribute of the MusicShelf.
|
||||||
|
|
||||||
|
The continuation response now uses a `onResponseReceivedActions` field for its music
|
||||||
|
items.
|
||||||
|
|
||||||
|
YouTube Music now also sends a random 16-character string as a `clientScreenNonce` in
|
||||||
|
the request context. This is not mandatory though.
|
||||||
|
|
|
||||||
|
|
@ -196,13 +196,15 @@ impl MapResponse<MusicPlaylist> for response::MusicPlaylist {
|
||||||
|
|
||||||
let mut mapper = MusicListMapper::new(ctx.lang);
|
let mut mapper = MusicListMapper::new(ctx.lang);
|
||||||
mapper.map_response(shelf.contents);
|
mapper.map_response(shelf.contents);
|
||||||
let map_res = mapper.conv_items();
|
|
||||||
|
|
||||||
let ctoken = shelf
|
let ctoken = mapper.ctoken.clone().or_else(|| {
|
||||||
.continuations
|
shelf
|
||||||
.into_iter()
|
.continuations
|
||||||
.next()
|
.into_iter()
|
||||||
.map(|cont| cont.next_continuation_data.continuation);
|
.next()
|
||||||
|
.map(|cont| cont.next_continuation_data.continuation)
|
||||||
|
});
|
||||||
|
let map_res = mapper.conv_items();
|
||||||
|
|
||||||
let track_count = if ctoken.is_some() {
|
let track_count = if ctoken.is_some() {
|
||||||
header.as_ref().and_then(|h| {
|
header.as_ref().and_then(|h| {
|
||||||
|
|
|
||||||
|
|
@ -189,6 +189,7 @@ impl<T: FromYtItem> MapResponse<MusicSearchResult<T>> for response::MusicSearch
|
||||||
response::music_search::ItemSection::None => {}
|
response::music_search::ItemSection::None => {}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let ctoken = ctoken.or(mapper.ctoken.clone());
|
||||||
let map_res = mapper.conv_items();
|
let map_res = mapper.conv_items();
|
||||||
|
|
||||||
Ok(MapResult {
|
Ok(MapResult {
|
||||||
|
|
|
||||||
|
|
@ -199,11 +199,17 @@ impl MapResponse<Paginator<MusicItem>> for response::MusicContinuation {
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for a in self.on_response_received_actions {
|
||||||
|
mapper.map_response(a.append_continuation_items_action.continuation_items);
|
||||||
|
}
|
||||||
|
|
||||||
|
let ctoken = mapper.ctoken.clone().or_else(|| {
|
||||||
|
continuations
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.map(|cont| cont.next_continuation_data.continuation)
|
||||||
|
});
|
||||||
let map_res = mapper.items();
|
let map_res = mapper.items();
|
||||||
let ctoken = continuations
|
|
||||||
.into_iter()
|
|
||||||
.next()
|
|
||||||
.map(|cont| cont.next_continuation_data.continuation);
|
|
||||||
|
|
||||||
Ok(MapResult {
|
Ok(MapResult {
|
||||||
c: Paginator::new_ext(
|
c: Paginator::new_ext(
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ use super::{
|
||||||
url_endpoint::{
|
url_endpoint::{
|
||||||
BrowseEndpointWrap, MusicPage, MusicPageType, MusicVideoType, NavigationEndpoint, PageType,
|
BrowseEndpointWrap, MusicPage, MusicPageType, MusicVideoType, NavigationEndpoint, PageType,
|
||||||
},
|
},
|
||||||
ContentsRenderer, MusicContinuationData, SimpleHeaderRenderer, Thumbnails, ThumbnailsWrap,
|
ContentsRenderer, ContinuationActionWrap, ContinuationEndpoint, MusicContinuationData,
|
||||||
|
SimpleHeaderRenderer, Thumbnails, ThumbnailsWrap,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
|
@ -86,6 +87,10 @@ pub(crate) enum MusicResponseItem {
|
||||||
MusicResponsiveListItemRenderer(ListMusicItem),
|
MusicResponsiveListItemRenderer(ListMusicItem),
|
||||||
MusicTwoRowItemRenderer(CoverMusicItem),
|
MusicTwoRowItemRenderer(CoverMusicItem),
|
||||||
MessageRenderer(serde::de::IgnoredAny),
|
MessageRenderer(serde::de::IgnoredAny),
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
ContinuationItemRenderer {
|
||||||
|
continuation_endpoint: ContinuationEndpoint,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
|
|
@ -322,10 +327,14 @@ impl From<MusicThumbnailRenderer> for Vec<model::Thumbnail> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Music list continuation response model
|
/// Music list continuation response model
|
||||||
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub(crate) struct MusicContinuation {
|
pub(crate) struct MusicContinuation {
|
||||||
pub continuation_contents: Option<ContinuationContents>,
|
pub continuation_contents: Option<ContinuationContents>,
|
||||||
|
#[serde(default)]
|
||||||
|
#[serde_as(as = "VecSkipError<_>")]
|
||||||
|
pub on_response_received_actions: Vec<ContinuationActionWrap<MusicResponseItem>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
|
@ -434,6 +443,7 @@ pub(crate) struct MusicListMapper {
|
||||||
search_suggestion: bool,
|
search_suggestion: bool,
|
||||||
items: Vec<MusicItem>,
|
items: Vec<MusicItem>,
|
||||||
warnings: Vec<String>,
|
warnings: Vec<String>,
|
||||||
|
pub ctoken: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -455,6 +465,7 @@ impl MusicListMapper {
|
||||||
search_suggestion: false,
|
search_suggestion: false,
|
||||||
items: Vec::new(),
|
items: Vec::new(),
|
||||||
warnings: Vec::new(),
|
warnings: Vec::new(),
|
||||||
|
ctoken: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -468,6 +479,7 @@ impl MusicListMapper {
|
||||||
search_suggestion: true,
|
search_suggestion: true,
|
||||||
items: Vec::new(),
|
items: Vec::new(),
|
||||||
warnings: Vec::new(),
|
warnings: Vec::new(),
|
||||||
|
ctoken: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -482,6 +494,7 @@ impl MusicListMapper {
|
||||||
search_suggestion: false,
|
search_suggestion: false,
|
||||||
items: Vec::new(),
|
items: Vec::new(),
|
||||||
warnings: Vec::new(),
|
warnings: Vec::new(),
|
||||||
|
ctoken: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -496,6 +509,7 @@ impl MusicListMapper {
|
||||||
search_suggestion: false,
|
search_suggestion: false,
|
||||||
items: Vec::new(),
|
items: Vec::new(),
|
||||||
warnings: Vec::new(),
|
warnings: Vec::new(),
|
||||||
|
ctoken: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -507,6 +521,12 @@ impl MusicListMapper {
|
||||||
// Tile
|
// Tile
|
||||||
MusicResponseItem::MusicTwoRowItemRenderer(item) => self.map_tile(item),
|
MusicResponseItem::MusicTwoRowItemRenderer(item) => self.map_tile(item),
|
||||||
MusicResponseItem::MessageRenderer(_) => Ok(None),
|
MusicResponseItem::MessageRenderer(_) => Ok(None),
|
||||||
|
MusicResponseItem::ContinuationItemRenderer {
|
||||||
|
continuation_endpoint,
|
||||||
|
} => {
|
||||||
|
self.ctoken = Some(continuation_endpoint.continuation_command.token);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ impl<T: FromYtItem> MapResponse<SearchResult<T>> for response::Search {
|
||||||
.filter_map(T::from_yt_item)
|
.filter_map(T::from_yt_item)
|
||||||
.collect(),
|
.collect(),
|
||||||
mapper.ctoken,
|
mapper.ctoken,
|
||||||
None,
|
ctx.visitor_data.map(str::to_owned),
|
||||||
ContinuationEndpoint::Search,
|
ContinuationEndpoint::Search,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -277,7 +277,7 @@ impl MapResponse<VideoDetails> for response::VideoDetails {
|
||||||
r,
|
r,
|
||||||
sr.secondary_results.continuations,
|
sr.secondary_results.continuations,
|
||||||
visitor_data.clone(),
|
visitor_data.clone(),
|
||||||
ctx.lang,
|
ctx,
|
||||||
);
|
);
|
||||||
warnings.append(&mut res.warnings);
|
warnings.append(&mut res.warnings);
|
||||||
res.c
|
res.c
|
||||||
|
|
@ -359,7 +359,7 @@ impl MapResponse<VideoDetails> for response::VideoDetails {
|
||||||
comment_ctoken,
|
comment_ctoken,
|
||||||
visitor_data.clone(),
|
visitor_data.clone(),
|
||||||
ContinuationEndpoint::Next,
|
ContinuationEndpoint::Next,
|
||||||
false,
|
ctx.authenticated,
|
||||||
),
|
),
|
||||||
latest_comments: Paginator::new_ext(
|
latest_comments: Paginator::new_ext(
|
||||||
comment_count,
|
comment_count,
|
||||||
|
|
@ -367,7 +367,7 @@ impl MapResponse<VideoDetails> for response::VideoDetails {
|
||||||
latest_comments_ctoken,
|
latest_comments_ctoken,
|
||||||
visitor_data.clone(),
|
visitor_data.clone(),
|
||||||
ContinuationEndpoint::Next,
|
ContinuationEndpoint::Next,
|
||||||
false,
|
ctx.authenticated,
|
||||||
),
|
),
|
||||||
visitor_data,
|
visitor_data,
|
||||||
},
|
},
|
||||||
|
|
@ -468,9 +468,9 @@ fn map_recommendations(
|
||||||
r: MapResult<Vec<response::YouTubeListItem>>,
|
r: MapResult<Vec<response::YouTubeListItem>>,
|
||||||
continuations: Option<Vec<response::MusicContinuationData>>,
|
continuations: Option<Vec<response::MusicContinuationData>>,
|
||||||
visitor_data: Option<String>,
|
visitor_data: Option<String>,
|
||||||
lang: Language,
|
ctx: &MapRespCtx<'_>,
|
||||||
) -> MapResult<Paginator<VideoItem>> {
|
) -> MapResult<Paginator<VideoItem>> {
|
||||||
let mut mapper = response::YouTubeListMapper::<VideoItem>::new(lang);
|
let mut mapper = response::YouTubeListMapper::<VideoItem>::new(ctx.lang);
|
||||||
mapper.map_response(r);
|
mapper.map_response(r);
|
||||||
|
|
||||||
mapper.ctoken = mapper.ctoken.or_else(|| {
|
mapper.ctoken = mapper.ctoken.or_else(|| {
|
||||||
|
|
@ -486,7 +486,7 @@ fn map_recommendations(
|
||||||
mapper.ctoken,
|
mapper.ctoken,
|
||||||
visitor_data,
|
visitor_data,
|
||||||
ContinuationEndpoint::Next,
|
ContinuationEndpoint::Next,
|
||||||
false,
|
ctx.authenticated,
|
||||||
),
|
),
|
||||||
warnings: mapper.warnings,
|
warnings: mapper.warnings,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Reference in a new issue