Compare commits

...

3 commits

Author SHA1 Message Date
4d124c6d98 fix: a/b test 11: parsing like count with new data model
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-11-04 02:47:33 +01:00
53cc9f1a27 fix: parsing Singhalese numbers 2023-11-04 02:03:17 +01:00
a1ac25fda5 fix: fetching channel info with different language 2023-11-04 01:36:49 +01:00
6 changed files with 112 additions and 8 deletions

View file

@ -27,6 +27,7 @@ pub enum ABTest {
TrackViewcount = 8,
PlaylistsForShorts = 9,
ChannelAboutModal = 10,
LikeButtonViewmodel = 11,
}
const TESTS_TO_RUN: [ABTest; 3] = [
@ -100,6 +101,7 @@ pub async fn run_test(
ABTest::PlaylistsForShorts => playlists_for_shorts(&query).await,
ABTest::TrackViewcount => track_viewcount(&query).await,
ABTest::ChannelAboutModal => channel_about_modal(&query).await,
ABTest::LikeButtonViewmodel => like_button_viewmodel(&query).await,
}
.unwrap();
pb.inc(1);
@ -301,3 +303,19 @@ pub async fn channel_about_modal(rp: &RustyPipeQuery) -> Result<bool> {
.unwrap();
Ok(!res.contains("\"EgVhYm91dPIGBAoCEgA%3D\""))
}
pub async fn like_button_viewmodel(rp: &RustyPipeQuery) -> Result<bool> {
let res = rp
.raw(
ClientType::Desktop,
"next",
&QVideo {
context: rp.get_context(ClientType::Desktop, true, None).await,
video_id: "ZeerrnuLi5E",
content_check_ok: true,
racy_check_ok: true,
},
)
.await?;
Ok(res.contains("\"segmentedLikeDislikeButtonViewModel\""))
}

View file

@ -432,3 +432,35 @@ channel metadata has to be fetched.
The new modal uses a continuation request with a token which can be easily generated.
Attempts to fetch the old about tab with the A/B test enabled will lead to a redirect to
the main tab.
## [11] Like-Button viewmodel
- **Encountered on:** 03.11.2023
- **Impact:** 🟢 Low
- **Endpoint:** next
YouTube introduced an updated date model for the like/dislike buttons. The new model
looks needlessly complex but contains the same parsing-relevant data as the old model
(accessibility text to get like count).
```json
{
"segmentedLikeDislikeButtonViewModel": {
"likeButtonViewModel": {
"likeButtonViewModel": {
"toggleButtonViewModel": {
"toggleButtonViewModel": {
"defaultButtonViewModel": {
"buttonViewModel": {
"iconName": "LIKE",
"title": "4.2M",
"accessibilityText": "like this video along with 4,209,059 other people"
}
}
}
}
}
}
}
}
```

View file

@ -291,10 +291,14 @@ impl MapResponse<ChannelInfo> for response::ChannelAbout {
fn map_response(
self,
_id: &str,
lang: Language,
_lang: Language,
_deobf: Option<&crate::deobfuscate::DeobfData>,
_visitor_data: Option<&str>,
) -> Result<MapResult<ChannelInfo>, ExtractionError> {
// Channel info is always fetched in English. There is no localized data there
// and it allows parsing the country name.
let lang = Language::En;
let ep = self
.on_response_received_endpoints
.into_iter()

View file

@ -147,6 +147,46 @@ pub(crate) enum TopLevelButton {
SegmentedLikeDislikeButtonRenderer {
like_button: ToggleButtonWrap,
},
#[serde(rename_all = "camelCase")]
SegmentedLikeDislikeButtonViewModel {
like_button_view_model: LikeButtonViewModelWrap,
},
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct LikeButtonViewModelWrap {
pub like_button_view_model: LikeButtonViewModel,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct LikeButtonViewModel {
pub toggle_button_view_model: ToggleButtonViewModelWrap,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ToggleButtonViewModelWrap {
pub toggle_button_view_model: ToggleButtonViewModel,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ToggleButtonViewModel {
pub default_button_view_model: ButtonViewModelWrap,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ButtonViewModelWrap {
pub button_view_model: ButtonViewModel,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ButtonViewModel {
pub accessibility_text: String,
}
/// Like/Dislike button

View file

@ -159,17 +159,20 @@ impl MapResponse<VideoDetails> for response::VideoDetails {
video_actions,
date_text,
}) => {
let like_btn = video_actions
let like_text = video_actions
.menu_renderer
.top_level_buttons
.into_iter()
.find_map(|button| {
let btn = match button {
response::video_details::TopLevelButton::ToggleButtonRenderer(btn) => btn,
response::video_details::TopLevelButton::SegmentedLikeDislikeButtonRenderer { like_button } => like_button.toggle_button_renderer,
let (icon, text) = match button {
response::video_details::TopLevelButton::ToggleButtonRenderer(btn) => (btn.default_icon.icon_type, btn.accessibility_data),
response::video_details::TopLevelButton::SegmentedLikeDislikeButtonRenderer { like_button } => (like_button.toggle_button_renderer.default_icon.icon_type, like_button.toggle_button_renderer.accessibility_data),
response::video_details::TopLevelButton::SegmentedLikeDislikeButtonViewModel { like_button_view_model } => {
(IconType::Like, like_button_view_model.like_button_view_model.toggle_button_view_model.toggle_button_view_model.default_button_view_model.button_view_model.accessibility_text)
},
};
match btn.default_icon.icon_type {
IconType::Like => Some(btn),
match icon {
IconType::Like => Some(text),
_ => None
}
});
@ -184,7 +187,7 @@ impl MapResponse<VideoDetails> for response::VideoDetails {
.unwrap_or_default(),
// accessibility_data contains no digits if the like count is hidden,
// so we ignore parse errors here for now
like_btn.and_then(|btn| util::parse_numeric(&btn.accessibility_data).ok()),
like_text.and_then(|txt| util::parse_numeric(&txt).ok()),
date_text.as_deref().and_then(|txt| {
timeago::parse_textual_date_or_warn(lang, txt, &mut warnings)
}),

View file

@ -362,6 +362,7 @@ where
let mut filtered = String::new();
let mut exp = 0;
let mut after_point = false;
let mut last_number = false;
for c in string.chars() {
if c.is_ascii_digit() {
@ -370,6 +371,10 @@ where
if after_point {
exp -= 1;
}
if !last_number {
filtered.push(' ');
last_number = true;
}
} else if c == decimal_point && !digits.is_empty() {
after_point = true;
} else if !matches!(
@ -377,6 +382,7 @@ where
'\u{200b}' | '\u{202b}' | '\u{202c}' | '\u{202e}' | '\u{200e}' | '\u{200f}' | '.' | ','
) {
c.to_lowercase().for_each(|c| filtered.push(c));
last_number = false;
}
}
@ -636,6 +642,7 @@ pub(crate) mod tests {
)]
#[case(Language::As, "১ জন গ্ৰাহক", 1)]
#[case(Language::Ru, "Зрителей, ожидающих начала трансляции: 6", 6)]
#[case(Language::Si, "වාදන මි4.6ක්", 4_600_000)]
fn t_parse_large_numstr(#[case] lang: Language, #[case] string: &str, #[case] expect: u64) {
let res = parse_large_numstr::<u64>(string, lang).unwrap();
assert_eq!(res, expect);