fix: A/B test 15 (parsing channel shortsLockupViewModel)

This commit is contained in:
ThetaDev 2024-09-10 03:12:16 +02:00
parent ed08f9ff9a
commit 7972df0df4
No known key found for this signature in database
GPG key ID: E319D3C5148D65B6
8 changed files with 14241 additions and 23 deletions

View file

@ -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();

View file

@ -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);

View file

@ -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 = ();