From 162959ca4513a03496776fae905b4bf20c79899c Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Wed, 18 Dec 2024 19:31:24 +0100 Subject: [PATCH] fix: remove leading zero-width-space from comments, ensure space after links --- src/deobfuscate.rs | 1 + src/serializer/text.rs | 43 ++++++++++++++++++++++++++++++++++++++---- tests/youtube.rs | 2 +- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/deobfuscate.rs b/src/deobfuscate.rs index c948887..dc1c2c8 100644 --- a/src/deobfuscate.rs +++ b/src/deobfuscate.rs @@ -250,6 +250,7 @@ fn extract_js_fn(js: &str, offset: usize, name: &str) -> Result DeserializeAs<'de, TextComponents> for AttributedText { .next() .map(Verification::from) .unwrap_or_default(); + let mut components: Vec = Vec::with_capacity(runs.len() + 1); + + fn process_txt_before(components: &[TextComponent], txt_before: Cow<'_, str>) -> String { + // YouTube sometimes inserts zero-width spaces at the start of comments + let txt_before = match txt_before.strip_prefix('\u{200b}') { + Some(t) => Cow::Borrowed(t), + None => txt_before, + }; + // Ensure that text after link components always begins with a space + if !txt_before + .chars() + .next() + .map(|c| c.is_whitespace()) + .unwrap_or_default() + && components + .last() + .map(|c| { + !matches!(c, TextComponent::Text { .. }) + && !c + .as_str() + .chars() + .last() + .map(|c| c.is_whitespace()) + .unwrap_or_default() + }) + .unwrap_or_default() + { + format!(" {txt_before}") + } else { + txt_before.into_owned() + } + } - let mut components = Vec::with_capacity(runs.len() + 1); for run in runs { - let txt_before = take_chars(run.start_index); + let txt_before = process_txt_before(&components, take_chars(run.start_index).into()); let txt_run = take_chars(run.start_index + run.length); if !txt_before.is_empty() { @@ -461,7 +492,7 @@ impl<'de> DeserializeAs<'de, TextComponents> for AttributedText { }) } - let end = chars.as_str(); + let end = process_txt_before(&components, chars.as_str().into()); if !end.is_empty() { components.push(TextComponent::new(end)); } @@ -683,6 +714,10 @@ impl From for String { } impl TextComponents { + pub fn is_empty(&self) -> bool { + self.0.iter().all(|c| c.as_str().is_empty()) + } + /// Return the string representation of the first text component pub fn first_str(&self) -> &str { self.0 diff --git a/tests/youtube.rs b/tests/youtube.rs index 5b663f4..b531cd7 100644 --- a/tests/youtube.rs +++ b/tests/youtube.rs @@ -30,7 +30,7 @@ use rustypipe::validate; #[case::desktop(ClientType::Desktop)] #[case::tv(ClientType::Tv)] #[case::mobile(ClientType::Mobile)] -// #[case::android(ClientType::Android)] +#[case::android(ClientType::Android)] #[case::ios(ClientType::Ios)] #[tokio::test] async fn get_player_from_client(#[case] client_type: ClientType, rp: RustyPipe) {