fix: A/B test 15 (parsing channel shortsLockupViewModel)
This commit is contained in:
parent
ed08f9ff9a
commit
7972df0df4
8 changed files with 14241 additions and 23 deletions
|
|
@ -34,6 +34,7 @@ pub enum ABTest {
|
|||
ChannelPageHeader = 12,
|
||||
MusicPlaylistTwoColumn = 13,
|
||||
CommentsFrameworkUpdate = 14,
|
||||
ChannelShortsLockup = 15,
|
||||
}
|
||||
|
||||
/// List of active A/B tests that are run when none is manually specified
|
||||
|
|
@ -110,6 +111,7 @@ pub async fn run_test(
|
|||
ABTest::ChannelPageHeader => channel_page_header(&query).await,
|
||||
ABTest::MusicPlaylistTwoColumn => music_playlist_two_column(&query).await,
|
||||
ABTest::CommentsFrameworkUpdate => comments_framework_update(&query).await,
|
||||
ABTest::ChannelShortsLockup => channel_shorts_lockup(&query).await,
|
||||
}
|
||||
.unwrap();
|
||||
pb.inc(1);
|
||||
|
|
@ -363,3 +365,20 @@ pub async fn comments_framework_update(rp: &RustyPipeQuery) -> Result<bool> {
|
|||
.unwrap();
|
||||
Ok(res.contains("\"frameworkUpdates\""))
|
||||
}
|
||||
|
||||
pub async fn channel_shorts_lockup(rp: &RustyPipeQuery) -> Result<bool> {
|
||||
let id = "UCh8gHdtzO2tXd593_bjErWg";
|
||||
let res = rp
|
||||
.raw(
|
||||
ClientType::Desktop,
|
||||
"browse",
|
||||
&QBrowse {
|
||||
context: rp.get_context(ClientType::Desktop, true, None).await,
|
||||
browse_id: id,
|
||||
params: Some("EgZzaG9ydHPyBgUKA5oBAA%3D%3D"),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
Ok(res.contains("\"shortsLockupViewModel\""))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -748,3 +748,42 @@ seperate framework update object
|
|||
}
|
||||
}
|
||||
```
|
||||
|
||||
## [15] Channel shorts: shortsLockupViewModel
|
||||
|
||||
- **Encountered on:** 10.09.2024
|
||||
- **Impact:** 🟢 Low
|
||||
- **Endpoint:** browse
|
||||
- **Status:** Common
|
||||
|
||||
YouTube changed the data model for the channel shorts tab
|
||||
|
||||
```json
|
||||
{
|
||||
"richItemRenderer": {
|
||||
"content": {
|
||||
"shortsLockupViewModel": {
|
||||
"entityId": "shorts-shelf-item-ovaHmfy3O6U",
|
||||
"accessibilityText": "hangover food, 17 million views - play Short",
|
||||
"thumbnail": {
|
||||
"sources": [
|
||||
{
|
||||
"url": "https://i.ytimg.com/vi/ovaHmfy3O6U/oar2.jpg?sqp=-oaymwEdCJUDENAFSFWQAgHyq4qpAwwIARUAAIhCcAHAAQY=&rs=AOn4CLBg-kG4rAi-BQ8Xkp2hOtOu-oXDLQ",
|
||||
"width": 405,
|
||||
"height": 720
|
||||
}
|
||||
]
|
||||
},
|
||||
"overlayMetadata": {
|
||||
"primaryText": {
|
||||
"content": "hangover food"
|
||||
},
|
||||
"secondaryText": {
|
||||
"content": "17M views"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -710,6 +710,7 @@ mod tests {
|
|||
#[case::livestreams("livestreams", "UC2DjFE7Xf11URZqWBigcVOQ")]
|
||||
#[case::pageheader("shorts_20240129_pageheader", "UCh8gHdtzO2tXd593_bjErWg")]
|
||||
#[case::pageheader2("videos_20240324_pageheader2", "UC2DjFE7Xf11URZqWBigcVOQ")]
|
||||
#[case::shorts2("shorts_20240910_lockup", "UCh8gHdtzO2tXd593_bjErWg")]
|
||||
fn map_channel_videos(#[case] name: &str, #[case] id: &str) {
|
||||
let json_path = path!(*TESTFILES / "channel" / format!("channel_{name}.json"));
|
||||
let json_file = File::open(json_path).unwrap();
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use crate::{
|
|||
},
|
||||
param::Language,
|
||||
serializer::{
|
||||
text::{Text, TextComponent},
|
||||
text::{AttributedText, Text, TextComponent},
|
||||
MapResult,
|
||||
},
|
||||
util::{self, timeago, TryRemove},
|
||||
|
|
@ -25,6 +25,7 @@ pub(crate) enum YouTubeListItem {
|
|||
#[serde(alias = "gridVideoRenderer", alias = "compactVideoRenderer")]
|
||||
VideoRenderer(VideoRenderer),
|
||||
ReelItemRenderer(ReelItemRenderer),
|
||||
ShortsLockupViewModel(ShortsLockupViewModel),
|
||||
PlaylistVideoRenderer(PlaylistVideoRenderer),
|
||||
|
||||
#[serde(alias = "gridPlaylistRenderer")]
|
||||
|
|
@ -142,6 +143,28 @@ pub(crate) struct ReelItemRenderer {
|
|||
pub navigation_endpoint: Option<ReelNavigationEndpoint>,
|
||||
}
|
||||
|
||||
// New short video item
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct ShortsLockupViewModel {
|
||||
/// `shorts-shelf-item-[video_id]`
|
||||
pub entity_id: String,
|
||||
pub thumbnail: Thumbnails,
|
||||
pub overlay_metadata: ShortsOverlayMetadata,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct ShortsOverlayMetadata {
|
||||
/// Title
|
||||
#[serde_as(as = "AttributedText")]
|
||||
pub primary_text: String,
|
||||
/// View count
|
||||
#[serde_as(as = "Option<AttributedText>")]
|
||||
pub secondary_text: Option<String>,
|
||||
}
|
||||
|
||||
/// Video displayed in a playlist
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
|
@ -517,6 +540,31 @@ impl<T> YouTubeListMapper<T> {
|
|||
}
|
||||
}
|
||||
|
||||
fn map_short_video2(&mut self, video: ShortsLockupViewModel) -> Option<VideoItem> {
|
||||
if let Some(video_id) = video.entity_id.strip_prefix("shorts-shelf-item-") {
|
||||
Some(VideoItem {
|
||||
id: video_id.to_owned(),
|
||||
name: video.overlay_metadata.primary_text,
|
||||
duration: None,
|
||||
thumbnail: video.thumbnail.into(),
|
||||
channel: self.channel.clone(),
|
||||
publish_date: None,
|
||||
publish_date_txt: None,
|
||||
view_count: video.overlay_metadata.secondary_text.and_then(|txt| {
|
||||
util::parse_large_numstr_or_warn(&txt, self.lang, &mut self.warnings)
|
||||
}),
|
||||
is_live: false,
|
||||
is_short: true,
|
||||
is_upcoming: false,
|
||||
short_description: None,
|
||||
})
|
||||
} else {
|
||||
self.warnings
|
||||
.push(format!("invalid shorts entityId: {}", video.entity_id));
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn map_playlist_video(&mut self, video: PlaylistVideoRenderer) -> VideoItem {
|
||||
let channel = ChannelId::try_from(video.channel)
|
||||
.ok()
|
||||
|
|
@ -642,6 +690,11 @@ impl YouTubeListMapper<YouTubeItem> {
|
|||
let mapped = YouTubeItem::Video(self.map_video(video));
|
||||
self.items.push(mapped);
|
||||
}
|
||||
YouTubeListItem::ShortsLockupViewModel(video) => {
|
||||
if let Some(mapped) = self.map_short_video2(video) {
|
||||
self.items.push(YouTubeItem::Video(mapped));
|
||||
}
|
||||
}
|
||||
YouTubeListItem::ReelItemRenderer(video) => {
|
||||
let mapped = self.map_short_video(video);
|
||||
self.items.push(YouTubeItem::Video(mapped));
|
||||
|
|
@ -692,6 +745,11 @@ impl YouTubeListMapper<VideoItem> {
|
|||
let mapped = self.map_short_video(video);
|
||||
self.items.push(mapped);
|
||||
}
|
||||
YouTubeListItem::ShortsLockupViewModel(video) => {
|
||||
if let Some(mapped) = self.map_short_video2(video) {
|
||||
self.items.push(mapped);
|
||||
}
|
||||
}
|
||||
YouTubeListItem::PlaylistVideoRenderer(video) => {
|
||||
let mapped = self.map_playlist_video(video);
|
||||
self.items.push(mapped);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -469,6 +469,19 @@ impl<'de> DeserializeAs<'de, TextComponent> for AttributedText {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeAs<'de, String> for AttributedText {
|
||||
fn deserialize_as<D>(deserializer: D) -> Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let components: TextComponents = AttributedText::deserialize_as(deserializer)?;
|
||||
Ok(components
|
||||
.0
|
||||
.into_iter()
|
||||
.fold(String::new(), |acc, c| acc + c.as_str()))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<TextComponent> for crate::model::ChannelId {
|
||||
type Error = ();
|
||||
|
||||
|
|
|
|||
12760
testfiles/channel/channel_shorts_20240910_lockup.json
Normal file
12760
testfiles/channel/channel_shorts_20240910_lockup.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -221,13 +221,13 @@ async fn check_video_stream(s: impl YtStream) {
|
|||
true
|
||||
)]
|
||||
#[case::agelimit(
|
||||
"laru0QoJUmI",
|
||||
"DJ Robin x Schürze - Layla (Official Video)",
|
||||
"Endlich ist es soweit! Zwei Männer aus dem Schwabenland",
|
||||
188,
|
||||
"UCkJfSrMnLonOZWh-q5os5bg",
|
||||
"Summerfield Records",
|
||||
10_000_000,
|
||||
"ZDKQmBWTRnw",
|
||||
"The Rinky Pink Pounder. Hitachi Magic Wand clone teardown.",
|
||||
"violent adult toys for disassembly",
|
||||
1333,
|
||||
"UCtM5z2gkrGRuWd0JQMx76qA",
|
||||
"bigclivedotcom",
|
||||
250_000,
|
||||
false,
|
||||
false
|
||||
)]
|
||||
|
|
@ -717,7 +717,7 @@ async fn get_video_details_live(rp: RustyPipe) {
|
|||
assert_eq!(details.id, "jfKfPfyJRdk");
|
||||
assert_eq!(
|
||||
details.name,
|
||||
"lofi hip hop radio 📚 - beats to relax/study to"
|
||||
"lofi hip hop radio 📚 beats to relax/study to"
|
||||
);
|
||||
let desc = details.description.to_plaintext();
|
||||
assert!(
|
||||
|
|
@ -752,24 +752,27 @@ async fn get_video_details_live(rp: RustyPipe) {
|
|||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn get_video_details_agegate(rp: RustyPipe) {
|
||||
let details = rp.query().video_details("laru0QoJUmI").await.unwrap();
|
||||
let details = rp.query().video_details("ZDKQmBWTRnw").await.unwrap();
|
||||
|
||||
// dbg!(&details);
|
||||
|
||||
assert_eq!(details.id, "laru0QoJUmI");
|
||||
assert_eq!(details.name, "DJ Robin x Schürze - Layla (Official Video)");
|
||||
assert_eq!(details.id, "ZDKQmBWTRnw");
|
||||
assert_eq!(
|
||||
details.name,
|
||||
"The Rinky Pink Pounder. Hitachi Magic Wand clone teardown."
|
||||
);
|
||||
insta::assert_ron_snapshot!(details.description, @"RichText([])");
|
||||
|
||||
assert_eq!(details.channel.id, "UCkJfSrMnLonOZWh-q5os5bg");
|
||||
assert_eq!(details.channel.name, "Summerfield Records");
|
||||
assert_eq!(details.channel.id, "UCtM5z2gkrGRuWd0JQMx76qA");
|
||||
assert_eq!(details.channel.name, "bigclivedotcom");
|
||||
assert!(!details.channel.avatar.is_empty(), "no channel avatars");
|
||||
assert_eq!(details.channel.verification, Verification::Verified);
|
||||
assert_gteo(details.channel.subscriber_count, 250_000, "subscribers");
|
||||
assert_gte(details.view_count, 10_000_000, "views");
|
||||
assert_gteo(details.like_count, 150_000, "likes");
|
||||
assert_gteo(details.channel.subscriber_count, 1_000_000, "subscribers");
|
||||
assert_gte(details.view_count, 250_000, "views");
|
||||
assert_gteo(details.like_count, 5_000, "likes");
|
||||
|
||||
let date = details.publish_date.expect("publish_date");
|
||||
assert_eq!(date.date(), date!(2022 - 5 - 13));
|
||||
assert_eq!(date.date(), date!(2017 - 3 - 09));
|
||||
|
||||
assert!(!details.is_live);
|
||||
assert!(!details.is_ccommons);
|
||||
|
|
@ -876,8 +879,11 @@ async fn channel_videos(rp: RustyPipe) {
|
|||
#[rstest]
|
||||
#[tokio::test]
|
||||
async fn channel_shorts(rp: RustyPipe) {
|
||||
let vd = rp.query().get_visitor_data().await.unwrap();
|
||||
|
||||
let channel = rp
|
||||
.query()
|
||||
.visitor_data(vd)
|
||||
.channel_videos_tab("UCh8gHdtzO2tXd593_bjErWg", ChannelVideoTab::Shorts)
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -2153,7 +2159,7 @@ async fn music_search_playlists(rp: RustyPipe, unlocalized: bool) {
|
|||
async fn music_search_playlists_community(rp: RustyPipe) {
|
||||
let res = rp
|
||||
.query()
|
||||
.music_search_playlists("Best Pop Music Videos - Top Pop Hits Playlist", true)
|
||||
.music_search_playlists("Miku my beloved (Jaiden Animation Miku Playlist)", true)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
|
@ -2162,20 +2168,20 @@ async fn music_search_playlists_community(rp: RustyPipe) {
|
|||
.items
|
||||
.items
|
||||
.iter()
|
||||
.find(|p| p.id == "PLMC9KNkIncKtGvr2kFRuXBVmBev6cAJ2u")
|
||||
.find(|p| p.id == "PLgAAMoX4rK3KhSGmIsN0LEoC3qowEr2Lz")
|
||||
.unwrap_or_else(|| {
|
||||
panic!("could not find playlist, got {:#?}", &res.items.items);
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
playlist.name,
|
||||
"Best Pop Music Videos - Top Pop Hits Playlist"
|
||||
"Miku my beloved (Jaiden Animation Miku Playlist)"
|
||||
);
|
||||
assert!(!playlist.thumbnail.is_empty(), "got no thumbnail");
|
||||
|
||||
let channel = playlist.channel.as_ref().unwrap();
|
||||
assert_eq!(channel.id, "UCs72iRpTEuwV3y6pdWYLgiw");
|
||||
assert_eq!(channel.name, "Redlist - Just Hits");
|
||||
assert_eq!(channel.id, "UCsXOMpqp3_ZPOmk-HGKEPRg");
|
||||
assert_eq!(channel.name, "Beanie Bean");
|
||||
assert!(!playlist.from_ytm);
|
||||
}
|
||||
|
||||
|
|
|
|||
Reference in a new issue