fix: add new attributed_text description
add mapping tests for video_details
This commit is contained in:
parent
0659a64361
commit
bf97f31173
19 changed files with 32747 additions and 9856 deletions
|
|
@ -1,6 +1,8 @@
|
|||
use std::convert::TryFrom;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use fancy_regex::Regex;
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use serde_with::{serde_as, DefaultOnError, DeserializeAs};
|
||||
|
||||
|
|
@ -120,6 +122,31 @@ struct RichTextRun {
|
|||
navigation_endpoint: NavigationEndpoint,
|
||||
}
|
||||
|
||||
/// This is a new rich text representation format that YouTube is A/B testing
|
||||
/// at the moment. It consists of the full text and an array of ranges describing
|
||||
/// the links.
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AttributedText {
|
||||
content: String,
|
||||
#[serde(default)]
|
||||
command_runs: Vec<AttributedTextRun>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct AttributedTextRun {
|
||||
start_index: usize,
|
||||
length: usize,
|
||||
on_tap: AttributedTextOnTap,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct AttributedTextOnTap {
|
||||
innertube_command: NavigationEndpoint,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
|
@ -198,38 +225,36 @@ pub enum PageType {
|
|||
Playlist,
|
||||
}
|
||||
|
||||
/// Map a single component of a rich text
|
||||
fn map_richtext_run(lr: &RichTextRun) -> Option<TextComponent> {
|
||||
let text = lr.text.to_owned();
|
||||
let nav = &lr.navigation_endpoint;
|
||||
fn map_richtext_run(run: RichTextRun) -> TextComponent {
|
||||
map_text_component(run.text, run.navigation_endpoint)
|
||||
}
|
||||
|
||||
Some(match &nav.watch_endpoint {
|
||||
/// Map a single component of a rich text
|
||||
fn map_text_component(text: String, nav: NavigationEndpoint) -> TextComponent {
|
||||
match nav.watch_endpoint {
|
||||
Some(w) => TextComponent::Video {
|
||||
text,
|
||||
video_id: w.video_id.to_owned(),
|
||||
video_id: w.video_id,
|
||||
start_time: w.start_time_seconds,
|
||||
},
|
||||
None => match &nav.browse_endpoint {
|
||||
None => match nav.browse_endpoint {
|
||||
Some(b) => TextComponent::Browse {
|
||||
text,
|
||||
page_type: match &b.browse_endpoint_context_supported_configs {
|
||||
Some(bc) => bc.browse_endpoint_context_music_config.page_type,
|
||||
None => match &nav.command_metadata {
|
||||
Some(cm) => cm.web_command_metadata.web_page_type,
|
||||
None => return None,
|
||||
None => return TextComponent::Text { text },
|
||||
},
|
||||
},
|
||||
browse_id: b.browse_id.to_owned(),
|
||||
text,
|
||||
browse_id: b.browse_id,
|
||||
},
|
||||
None => match &nav.url_endpoint {
|
||||
Some(u) => TextComponent::Web {
|
||||
text,
|
||||
url: u.url.to_owned(),
|
||||
},
|
||||
None => match nav.url_endpoint {
|
||||
Some(u) => TextComponent::Web { text, url: u.url },
|
||||
None => TextComponent::Text { text },
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for TextComponent {
|
||||
|
|
@ -237,18 +262,15 @@ impl<'de> Deserialize<'de> for TextComponent {
|
|||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let link = RichTextInternal::deserialize(deserializer)?;
|
||||
if link.runs.len() != 1 {
|
||||
let mut text = RichTextInternal::deserialize(deserializer)?;
|
||||
if text.runs.len() != 1 {
|
||||
return Err(serde::de::Error::invalid_length(
|
||||
link.runs.len(),
|
||||
&"1 run, use RichText for more",
|
||||
text.runs.len(),
|
||||
&"1 run, use TextComponents for more",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(some_or_bail!(
|
||||
map_richtext_run(&link.runs[0]),
|
||||
Err(serde::de::Error::custom("missing/invalid browse endpoint"))
|
||||
))
|
||||
Ok(map_richtext_run(text.runs.swap_remove(0)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -257,10 +279,85 @@ impl<'de> Deserialize<'de> for TextComponents {
|
|||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let link = RichTextInternal::deserialize(deserializer)?;
|
||||
Ok(Self(
|
||||
link.runs.iter().filter_map(map_richtext_run).collect(),
|
||||
))
|
||||
let text = RichTextInternal::deserialize(deserializer)?;
|
||||
Ok(Self(text.runs.into_iter().map(map_richtext_run).collect()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> DeserializeAs<'de, TextComponents> for AttributedText {
|
||||
fn deserialize_as<D>(deserializer: D) -> Result<TextComponents, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let text = AttributedText::deserialize(deserializer)?;
|
||||
|
||||
let mut i_utf16 = 0;
|
||||
let mut chars = text.content.chars();
|
||||
|
||||
// Take a string from the char iterator until the given
|
||||
// UTF-16 index. This mimicks the Javascript substring behavior.
|
||||
let mut take_chars = |until: usize| {
|
||||
if until <= i_utf16 {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
let mut buf = String::with_capacity(until - i_utf16);
|
||||
loop {
|
||||
match chars.next() {
|
||||
Some(c) => {
|
||||
buf.push(c);
|
||||
|
||||
// is character on Basic Multilingual Plane -> 16bit in UTF-16,
|
||||
// counts as 1 JS character, otherwise 32bit, counts as 2 JS characters
|
||||
if (c as u32) > 0xffff {
|
||||
i_utf16 += 1;
|
||||
};
|
||||
i_utf16 += 1;
|
||||
|
||||
if i_utf16 >= until {
|
||||
break;
|
||||
}
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
buf
|
||||
};
|
||||
|
||||
let mut components = Vec::with_capacity(text.command_runs.len() + 1);
|
||||
text.command_runs.into_iter().for_each(|cmd| {
|
||||
let txt_before = take_chars(cmd.start_index);
|
||||
let txt_link = take_chars(cmd.start_index + cmd.length);
|
||||
|
||||
// Trim link text:
|
||||
// 3xnbsp, (/ •), nbsp, Name, 2xnbsp
|
||||
// Channel: `\u{a0}\u{a0}\u{a0}/\u{a0}aespa\u{a0}\u{a0}`
|
||||
// Video: `\u{a0}\u{a0}\u{a0}•\u{a0}aespa\u{a0}에스파\u{a0}'Black\u{a0}...\u{a0}\u{a0}`
|
||||
|
||||
// Replace no-break spaces, trim off whitespace and prefix character
|
||||
let txt_link = txt_link.trim();
|
||||
let txt_link = txt_link.replace("\u{a0}", " ");
|
||||
|
||||
static LINK_PREFIX: Lazy<Regex> = Lazy::new(|| Regex::new(r#"^(?:\/|•) *"#).unwrap());
|
||||
let txt_link = LINK_PREFIX.replace(&txt_link, "");
|
||||
|
||||
if !txt_before.is_empty() {
|
||||
components.push(TextComponent::Text { text: txt_before });
|
||||
}
|
||||
components.push(map_text_component(
|
||||
txt_link.to_string(),
|
||||
cmd.on_tap.innertube_command,
|
||||
));
|
||||
});
|
||||
|
||||
let end = chars.as_str();
|
||||
if !end.is_empty() {
|
||||
components.push(TextComponent::Text {
|
||||
text: end.to_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(TextComponents(components))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -431,6 +528,13 @@ mod tests {
|
|||
ln: TextComponents,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SAttributed {
|
||||
#[serde_as(as = "AttributedText")]
|
||||
ln: TextComponents,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn t_link_video() {
|
||||
let test_json = r#"{
|
||||
|
|
@ -647,4 +751,468 @@ mod tests {
|
|||
}
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn t_attributed_description() {
|
||||
let test_json = r#"{
|
||||
"ln": {
|
||||
"content": "🎧Listen and download aespa's debut single \"Black Mamba\": https://smarturl.it/aespa_BlackMamba\n🐍The Debut Stage • aespa 에스파 'Black ... \n\n🎟️ aespa Showcase SYNK in LA! Tickets now on sale: https://www.ticketmaster.com/event/0A...\n\nSubscribe to aespa Official YouTube Channel!\nhttps://www.youtube.com/aespa?sub_con...\n\naespa official\n / aespa \nhttps://www.instagram.com/aespa_official\nhttps://www.tiktok.com/@aespa_official\nhttps://twitter.com/aespa_Official\nhttps://www.facebook.com/aespa.official\nhttps://weibo.com/aespa\n\n#aespa #æspa #BlackMamba #블랙맘바 #에스파\naespa 에스파 'Black Mamba' MV ℗ SM Entertainment",
|
||||
"commandRuns": [
|
||||
{
|
||||
"startIndex": 58,
|
||||
"length": 36,
|
||||
"onTap": {
|
||||
"innertubeCommand": {
|
||||
"clickTrackingParams": "CJ0BEM2rARgBIhMIzvHr0sis-gIV0kZ6BR0GNA_4SJGXrtzn9erzZQ==",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"url": "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbm1qRVVfQUlObURLcnFFQXBTUkJSOEpqWGIzUXxBQ3Jtc0tsNUJIYm5xdERxZk9rZEw3YlJzV0ZIYTNaSjU2a21PaFhNUmxzdjI5VE1VRWUyczZwYmtmQXh3QXV0eXlkMDgxRUJoNVMzRFZ6RlZ6MGdXeXdWQXFTTGY2ZHhFcUFqdExRQ21PYzNfWmlBaHhqYXVUdw&q=https%3A%2F%2Fsmarturl.it%2Faespa_BlackMamba&v=ZeerrnuLi5E",
|
||||
"webPageType": "WEB_PAGE_TYPE_UNKNOWN",
|
||||
"rootVe": 83769
|
||||
}
|
||||
},
|
||||
"urlEndpoint": {
|
||||
"url": "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbm1qRVVfQUlObURLcnFFQXBTUkJSOEpqWGIzUXxBQ3Jtc0tsNUJIYm5xdERxZk9rZEw3YlJzV0ZIYTNaSjU2a21PaFhNUmxzdjI5VE1VRWUyczZwYmtmQXh3QXV0eXlkMDgxRUJoNVMzRFZ6RlZ6MGdXeXdWQXFTTGY2ZHhFcUFqdExRQ21PYzNfWmlBaHhqYXVUdw&q=https%3A%2F%2Fsmarturl.it%2Faespa_BlackMamba&v=ZeerrnuLi5E",
|
||||
"target": "TARGET_NEW_WINDOW",
|
||||
"nofollow": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"startIndex": 113,
|
||||
"length": 27,
|
||||
"onTap": {
|
||||
"innertubeCommand": {
|
||||
"clickTrackingParams": "CJ0BEM2rARgBIhMIzvHr0sis-gIV0kZ6BR0GNA_4",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"url": "/watch?v=Ky5RT5oGg0w&t=0s",
|
||||
"webPageType": "WEB_PAGE_TYPE_WATCH",
|
||||
"rootVe": 3832
|
||||
}
|
||||
},
|
||||
"watchEndpoint": {
|
||||
"videoId": "Ky5RT5oGg0w",
|
||||
"startTimeSeconds": 0,
|
||||
"watchEndpointSupportedOnesieConfig": {
|
||||
"html5PlaybackOnesieConfig": {
|
||||
"commonConfig": {
|
||||
"url": "https://rr5---sn-h0jeener.googlevideo.com/initplayback?source=youtube&orc=1&oeis=1&c=WEB&oad=3200&ovd=3200&oaad=11000&oavd=11000&ocs=700&oewis=1&oputc=1&ofpcc=1&msp=1&odeak=1&odepv=1&osfc=1&id=2b2e514f9a06834c&ip=2003%3Ade%3Aaf30%3A200%3Ad8ce%3A4044%3A2ba2%3A3881&initcwndbps=1556250&mt=1663992556&oweuc="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"startIndex": 194,
|
||||
"length": 40,
|
||||
"onTap": {
|
||||
"innertubeCommand": {
|
||||
"clickTrackingParams": "CJ0BEM2rARgBIhMIzvHr0sis-gIV0kZ6BR0GNA_4SJGXrtzn9erzZQ==",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"url": "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbU1ObGNaRDZaRmo1X1ZjejBoeTRnWkxuVUJxZ3xBQ3Jtc0ttWk1BVVhaRXRfN1VYWXBqMHdaYURTRFJNcUZJVlY3a21wRHE2ZGZaclE3WUM5bEZWbmFfT0sxWTZHOVotWVh6U3YtVk94SlA5NkRFTnBPcHVCWDJhMGJRQlI3ZHN0MnJleHp0c2lEVWNxeW1jSDZuVQ&q=https%3A%2F%2Fwww.ticketmaster.com%2Fevent%2F0A005CCD9E871F6E&v=ZeerrnuLi5E",
|
||||
"webPageType": "WEB_PAGE_TYPE_UNKNOWN",
|
||||
"rootVe": 83769
|
||||
}
|
||||
},
|
||||
"urlEndpoint": {
|
||||
"url": "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbU1ObGNaRDZaRmo1X1ZjejBoeTRnWkxuVUJxZ3xBQ3Jtc0ttWk1BVVhaRXRfN1VYWXBqMHdaYURTRFJNcUZJVlY3a21wRHE2ZGZaclE3WUM5bEZWbmFfT0sxWTZHOVotWVh6U3YtVk94SlA5NkRFTnBPcHVCWDJhMGJRQlI3ZHN0MnJleHp0c2lEVWNxeW1jSDZuVQ&q=https%3A%2F%2Fwww.ticketmaster.com%2Fevent%2F0A005CCD9E871F6E&v=ZeerrnuLi5E",
|
||||
"target": "TARGET_NEW_WINDOW",
|
||||
"nofollow": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"startIndex": 281,
|
||||
"length": 40,
|
||||
"onTap": {
|
||||
"innertubeCommand": {
|
||||
"clickTrackingParams": "CJ0BEM2rARgBIhMIzvHr0sis-gIV0kZ6BR0GNA_4",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"url": "https://www.youtube.com/aespa?sub_confirmation=1",
|
||||
"webPageType": "WEB_PAGE_TYPE_UNKNOWN",
|
||||
"rootVe": 83769
|
||||
}
|
||||
},
|
||||
"urlEndpoint": {
|
||||
"url": "https://www.youtube.com/aespa?sub_confirmation=1",
|
||||
"nofollow": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"startIndex": 338,
|
||||
"length": 12,
|
||||
"onTap": {
|
||||
"innertubeCommand": {
|
||||
"clickTrackingParams": "CJ0BEM2rARgBIhMIzvHr0sis-gIV0kZ6BR0GNA_4",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"url": "https://www.youtube.com/c/aespa",
|
||||
"webPageType": "WEB_PAGE_TYPE_UNKNOWN",
|
||||
"rootVe": 83769
|
||||
}
|
||||
},
|
||||
"urlEndpoint": {
|
||||
"url": "https://www.youtube.com/c/aespa",
|
||||
"nofollow": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"startIndex": 351,
|
||||
"length": 40,
|
||||
"onTap": {
|
||||
"innertubeCommand": {
|
||||
"clickTrackingParams": "CJ0BEM2rARgBIhMIzvHr0sis-gIV0kZ6BR0GNA_4SJGXrtzn9erzZQ==",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"url": "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbE9FVEtZZkVLUExjdFBnZjZnZ19KNWRYOVZUd3xBQ3Jtc0tsbHpCa1hLTVJ6MEllczlzUEpoVi1IQ2F5NG1jMnlOT3p3bnlFeE80ZzlsaG5CUXlFQnFGTkMtN19DcVYzQkw3bVlVVmNwQlpYQWZnNGNsME45WE1WQ21sR3V1Z3k5RG9DUDE0VTZQTm53Mk9vTWhiOA&q=https%3A%2F%2Fwww.instagram.com%2Faespa_official&v=ZeerrnuLi5E",
|
||||
"webPageType": "WEB_PAGE_TYPE_UNKNOWN",
|
||||
"rootVe": 83769
|
||||
}
|
||||
},
|
||||
"urlEndpoint": {
|
||||
"url": "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbE9FVEtZZkVLUExjdFBnZjZnZ19KNWRYOVZUd3xBQ3Jtc0tsbHpCa1hLTVJ6MEllczlzUEpoVi1IQ2F5NG1jMnlOT3p3bnlFeE80ZzlsaG5CUXlFQnFGTkMtN19DcVYzQkw3bVlVVmNwQlpYQWZnNGNsME45WE1WQ21sR3V1Z3k5RG9DUDE0VTZQTm53Mk9vTWhiOA&q=https%3A%2F%2Fwww.instagram.com%2Faespa_official&v=ZeerrnuLi5E",
|
||||
"target": "TARGET_NEW_WINDOW",
|
||||
"nofollow": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"startIndex": 392,
|
||||
"length": 38,
|
||||
"onTap": {
|
||||
"innertubeCommand": {
|
||||
"clickTrackingParams": "CJ0BEM2rARgBIhMIzvHr0sis-gIV0kZ6BR0GNA_4SJGXrtzn9erzZQ==",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"url": "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbVdlSGk3eDd5U0dUVG16VFJCQnhKVFBEUUxMQXxBQ3Jtc0tuX3ZJbENNY1ZSN0FFemdxTFdlcTVvc3AwZE05NEFvRW5nOHpZWDUtZG9ORHBnT1JGc2UySDh3WWl3MU53VjFvbHRSdjdxMUlGM2Z6SmdaLTVaWWxhamJEems0Uld3MGlTT0Z0bkh5Y0hpcnY1aXptSQ&q=https%3A%2F%2Fwww.tiktok.com%2F%40aespa_official&v=ZeerrnuLi5E",
|
||||
"webPageType": "WEB_PAGE_TYPE_UNKNOWN",
|
||||
"rootVe": 83769
|
||||
}
|
||||
},
|
||||
"urlEndpoint": {
|
||||
"url": "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbVdlSGk3eDd5U0dUVG16VFJCQnhKVFBEUUxMQXxBQ3Jtc0tuX3ZJbENNY1ZSN0FFemdxTFdlcTVvc3AwZE05NEFvRW5nOHpZWDUtZG9ORHBnT1JGc2UySDh3WWl3MU53VjFvbHRSdjdxMUlGM2Z6SmdaLTVaWWxhamJEems0Uld3MGlTT0Z0bkh5Y0hpcnY1aXptSQ&q=https%3A%2F%2Fwww.tiktok.com%2F%40aespa_official&v=ZeerrnuLi5E",
|
||||
"target": "TARGET_NEW_WINDOW",
|
||||
"nofollow": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"startIndex": 431,
|
||||
"length": 34,
|
||||
"onTap": {
|
||||
"innertubeCommand": {
|
||||
"clickTrackingParams": "CJ0BEM2rARgBIhMIzvHr0sis-gIV0kZ6BR0GNA_4SJGXrtzn9erzZQ==",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"url": "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqa3lNcG1lMHkwSzNLQVBrUXFNTXl0N1hNa04wUXxBQ3Jtc0tubm1sQkdaVjNYR04xOHpJV3NxZVBpb3I5V1FVOHVFNC1uWE5vb211ZmZKYzhTZXZfbjlkY09fanBRdHpjUkdRVGJJYS0xZ3NBNkVZQVhWSS0xVDYwRlRzQ0J3ODQxNDE0ODAxd1Q0cG5icVlNWndscw&q=https%3A%2F%2Ftwitter.com%2Faespa_Official&v=ZeerrnuLi5E",
|
||||
"webPageType": "WEB_PAGE_TYPE_UNKNOWN",
|
||||
"rootVe": 83769
|
||||
}
|
||||
},
|
||||
"urlEndpoint": {
|
||||
"url": "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqa3lNcG1lMHkwSzNLQVBrUXFNTXl0N1hNa04wUXxBQ3Jtc0tubm1sQkdaVjNYR04xOHpJV3NxZVBpb3I5V1FVOHVFNC1uWE5vb211ZmZKYzhTZXZfbjlkY09fanBRdHpjUkdRVGJJYS0xZ3NBNkVZQVhWSS0xVDYwRlRzQ0J3ODQxNDE0ODAxd1Q0cG5icVlNWndscw&q=https%3A%2F%2Ftwitter.com%2Faespa_Official&v=ZeerrnuLi5E",
|
||||
"target": "TARGET_NEW_WINDOW",
|
||||
"nofollow": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"startIndex": 466,
|
||||
"length": 39,
|
||||
"onTap": {
|
||||
"innertubeCommand": {
|
||||
"clickTrackingParams": "CJ0BEM2rARgBIhMIzvHr0sis-gIV0kZ6BR0GNA_4SJGXrtzn9erzZQ==",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"url": "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbjdBNG5yVEFwU0JMNGZaLUpQZ1ZoeGgwT0xOZ3xBQ3Jtc0tuRFdFNlJNV29PMThRNWo5MHZrREZ1ZU5oZlkxVmE4ZlU5STFCZW1mUFVSdXJ3VUQxUnNVVkUzLWJQMS1uRzVjdkRCV2ZxSWJ6cFNxRVVzejY0SDltZFZPc2xwS3ZPZGIxcFZ6cndIVkMtUjVtZ054cw&q=https%3A%2F%2Fwww.facebook.com%2Faespa.official&v=ZeerrnuLi5E",
|
||||
"webPageType": "WEB_PAGE_TYPE_UNKNOWN",
|
||||
"rootVe": 83769
|
||||
}
|
||||
},
|
||||
"urlEndpoint": {
|
||||
"url": "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbjdBNG5yVEFwU0JMNGZaLUpQZ1ZoeGgwT0xOZ3xBQ3Jtc0tuRFdFNlJNV29PMThRNWo5MHZrREZ1ZU5oZlkxVmE4ZlU5STFCZW1mUFVSdXJ3VUQxUnNVVkUzLWJQMS1uRzVjdkRCV2ZxSWJ6cFNxRVVzejY0SDltZFZPc2xwS3ZPZGIxcFZ6cndIVkMtUjVtZ054cw&q=https%3A%2F%2Fwww.facebook.com%2Faespa.official&v=ZeerrnuLi5E",
|
||||
"target": "TARGET_NEW_WINDOW",
|
||||
"nofollow": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"startIndex": 506,
|
||||
"length": 23,
|
||||
"onTap": {
|
||||
"innertubeCommand": {
|
||||
"clickTrackingParams": "CJ0BEM2rARgBIhMIzvHr0sis-gIV0kZ6BR0GNA_4SJGXrtzn9erzZQ==",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"url": "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbEtGMHB6eXBESW92aEVLc1FybkRwQU95eTh6UXxBQ3Jtc0tuWXc5d2JsTHFYcHExdy1FTDFyUV9wdU1DSmxELUxGSGlPMzhBdFVkblRSZkNLQzRaMEJGUGhYLWp4RU40YUVwV3N3ZUpRTVVKVDRiY19zeE5RUkt2dW5aUVcxcHBRQldCOTE3YktXSXZlSFJhRWRjdw&q=https%3A%2F%2Fweibo.com%2Faespa&v=ZeerrnuLi5E",
|
||||
"webPageType": "WEB_PAGE_TYPE_UNKNOWN",
|
||||
"rootVe": 83769
|
||||
}
|
||||
},
|
||||
"urlEndpoint": {
|
||||
"url": "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbEtGMHB6eXBESW92aEVLc1FybkRwQU95eTh6UXxBQ3Jtc0tuWXc5d2JsTHFYcHExdy1FTDFyUV9wdU1DSmxELUxGSGlPMzhBdFVkblRSZkNLQzRaMEJGUGhYLWp4RU40YUVwV3N3ZUpRTVVKVDRiY19zeE5RUkt2dW5aUVcxcHBRQldCOTE3YktXSXZlSFJhRWRjdw&q=https%3A%2F%2Fweibo.com%2Faespa&v=ZeerrnuLi5E",
|
||||
"target": "TARGET_NEW_WINDOW",
|
||||
"nofollow": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"startIndex": 531,
|
||||
"length": 6,
|
||||
"onTap": {
|
||||
"innertubeCommand": {
|
||||
"clickTrackingParams": "CKIBENzXBBgKIhMIzvHr0sis-gIV0kZ6BR0GNA_4",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"url": "/hashtag/aespa",
|
||||
"webPageType": "WEB_PAGE_TYPE_BROWSE",
|
||||
"rootVe": 6827,
|
||||
"apiUrl": "/youtubei/v1/browse"
|
||||
}
|
||||
},
|
||||
"browseEndpoint": {
|
||||
"browseId": "FEhashtag",
|
||||
"params": "6gUHCgVhZXNwYQ%3D%3D"
|
||||
}
|
||||
}
|
||||
},
|
||||
"loggingDirectives": {
|
||||
"trackingParams": "CKIBENzXBBgKIhMIzvHr0sis-gIV0kZ6BR0GNA_4",
|
||||
"enableDisplayloggerExperiment": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"startIndex": 538,
|
||||
"length": 5,
|
||||
"onTap": {
|
||||
"innertubeCommand": {
|
||||
"clickTrackingParams": "CKEBENzXBBgLIhMIzvHr0sis-gIV0kZ6BR0GNA_4",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"url": "/hashtag/%C3%A6spa",
|
||||
"webPageType": "WEB_PAGE_TYPE_BROWSE",
|
||||
"rootVe": 6827,
|
||||
"apiUrl": "/youtubei/v1/browse"
|
||||
}
|
||||
},
|
||||
"browseEndpoint": {
|
||||
"browseId": "FEhashtag",
|
||||
"params": "6gUHCgXDpnNwYQ%3D%3D"
|
||||
}
|
||||
}
|
||||
},
|
||||
"loggingDirectives": {
|
||||
"trackingParams": "CKEBENzXBBgLIhMIzvHr0sis-gIV0kZ6BR0GNA_4",
|
||||
"enableDisplayloggerExperiment": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"startIndex": 544,
|
||||
"length": 11,
|
||||
"onTap": {
|
||||
"innertubeCommand": {
|
||||
"clickTrackingParams": "CKABENzXBBgMIhMIzvHr0sis-gIV0kZ6BR0GNA_4",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"url": "/hashtag/blackmamba",
|
||||
"webPageType": "WEB_PAGE_TYPE_BROWSE",
|
||||
"rootVe": 6827,
|
||||
"apiUrl": "/youtubei/v1/browse"
|
||||
}
|
||||
},
|
||||
"browseEndpoint": {
|
||||
"browseId": "FEhashtag",
|
||||
"params": "6gUMCgpibGFja21hbWJh"
|
||||
}
|
||||
}
|
||||
},
|
||||
"loggingDirectives": {
|
||||
"trackingParams": "CKABENzXBBgMIhMIzvHr0sis-gIV0kZ6BR0GNA_4",
|
||||
"enableDisplayloggerExperiment": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"startIndex": 556,
|
||||
"length": 5,
|
||||
"onTap": {
|
||||
"innertubeCommand": {
|
||||
"clickTrackingParams": "CJ8BENzXBBgNIhMIzvHr0sis-gIV0kZ6BR0GNA_4",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"url": "/hashtag/%EB%B8%94%EB%9E%99%EB%A7%98%EB%B0%94",
|
||||
"webPageType": "WEB_PAGE_TYPE_BROWSE",
|
||||
"rootVe": 6827,
|
||||
"apiUrl": "/youtubei/v1/browse"
|
||||
}
|
||||
},
|
||||
"browseEndpoint": {
|
||||
"browseId": "FEhashtag",
|
||||
"params": "6gUOCgzruJTrnpnrp5jrsJQ%3D"
|
||||
}
|
||||
}
|
||||
},
|
||||
"loggingDirectives": {
|
||||
"trackingParams": "CJ8BENzXBBgNIhMIzvHr0sis-gIV0kZ6BR0GNA_4",
|
||||
"enableDisplayloggerExperiment": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"startIndex": 562,
|
||||
"length": 4,
|
||||
"onTap": {
|
||||
"innertubeCommand": {
|
||||
"clickTrackingParams": "CJ4BENzXBBgOIhMIzvHr0sis-gIV0kZ6BR0GNA_4",
|
||||
"commandMetadata": {
|
||||
"webCommandMetadata": {
|
||||
"url": "/hashtag/%EC%97%90%EC%8A%A4%ED%8C%8C",
|
||||
"webPageType": "WEB_PAGE_TYPE_BROWSE",
|
||||
"rootVe": 6827,
|
||||
"apiUrl": "/youtubei/v1/browse"
|
||||
}
|
||||
},
|
||||
"browseEndpoint": {
|
||||
"browseId": "FEhashtag",
|
||||
"params": "6gULCgnsl5DsiqTtjIw%3D"
|
||||
}
|
||||
}
|
||||
},
|
||||
"loggingDirectives": {
|
||||
"trackingParams": "CJ4BENzXBBgOIhMIzvHr0sis-gIV0kZ6BR0GNA_4",
|
||||
"enableDisplayloggerExperiment": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}"#;
|
||||
|
||||
let res = serde_json::from_str::<SAttributed>(test_json).unwrap();
|
||||
insta::assert_debug_snapshot!(res, @r###"
|
||||
SAttributed {
|
||||
ln: TextComponents(
|
||||
[
|
||||
Text {
|
||||
text: "🎧Listen and download aespa's debut single \"Black Mamba\": ",
|
||||
},
|
||||
Web {
|
||||
text: "https://smarturl.it/aespa_BlackMamba",
|
||||
url: "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbm1qRVVfQUlObURLcnFFQXBTUkJSOEpqWGIzUXxBQ3Jtc0tsNUJIYm5xdERxZk9rZEw3YlJzV0ZIYTNaSjU2a21PaFhNUmxzdjI5VE1VRWUyczZwYmtmQXh3QXV0eXlkMDgxRUJoNVMzRFZ6RlZ6MGdXeXdWQXFTTGY2ZHhFcUFqdExRQ21PYzNfWmlBaHhqYXVUdw&q=https%3A%2F%2Fsmarturl.it%2Faespa_BlackMamba&v=ZeerrnuLi5E",
|
||||
},
|
||||
Text {
|
||||
text: "\n🐍The Debut Stage ",
|
||||
},
|
||||
Video {
|
||||
text: "aespa 에스파 'Black ...",
|
||||
video_id: "Ky5RT5oGg0w",
|
||||
start_time: 0,
|
||||
},
|
||||
Text {
|
||||
text: "\n\n🎟\u{fe0f} aespa Showcase SYNK in LA! Tickets now on sale: ",
|
||||
},
|
||||
Web {
|
||||
text: "https://www.ticketmaster.com/event/0A...",
|
||||
url: "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbU1ObGNaRDZaRmo1X1ZjejBoeTRnWkxuVUJxZ3xBQ3Jtc0ttWk1BVVhaRXRfN1VYWXBqMHdaYURTRFJNcUZJVlY3a21wRHE2ZGZaclE3WUM5bEZWbmFfT0sxWTZHOVotWVh6U3YtVk94SlA5NkRFTnBPcHVCWDJhMGJRQlI3ZHN0MnJleHp0c2lEVWNxeW1jSDZuVQ&q=https%3A%2F%2Fwww.ticketmaster.com%2Fevent%2F0A005CCD9E871F6E&v=ZeerrnuLi5E",
|
||||
},
|
||||
Text {
|
||||
text: "\n\nSubscribe to aespa Official YouTube Channel!\n",
|
||||
},
|
||||
Web {
|
||||
text: "https://www.youtube.com/aespa?sub_con...",
|
||||
url: "https://www.youtube.com/aespa?sub_confirmation=1",
|
||||
},
|
||||
Text {
|
||||
text: "\n\naespa official\n",
|
||||
},
|
||||
Web {
|
||||
text: "aespa",
|
||||
url: "https://www.youtube.com/c/aespa",
|
||||
},
|
||||
Text {
|
||||
text: "\n",
|
||||
},
|
||||
Web {
|
||||
text: "https://www.instagram.com/aespa_official",
|
||||
url: "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbE9FVEtZZkVLUExjdFBnZjZnZ19KNWRYOVZUd3xBQ3Jtc0tsbHpCa1hLTVJ6MEllczlzUEpoVi1IQ2F5NG1jMnlOT3p3bnlFeE80ZzlsaG5CUXlFQnFGTkMtN19DcVYzQkw3bVlVVmNwQlpYQWZnNGNsME45WE1WQ21sR3V1Z3k5RG9DUDE0VTZQTm53Mk9vTWhiOA&q=https%3A%2F%2Fwww.instagram.com%2Faespa_official&v=ZeerrnuLi5E",
|
||||
},
|
||||
Text {
|
||||
text: "\n",
|
||||
},
|
||||
Web {
|
||||
text: "https://www.tiktok.com/@aespa_official",
|
||||
url: "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbVdlSGk3eDd5U0dUVG16VFJCQnhKVFBEUUxMQXxBQ3Jtc0tuX3ZJbENNY1ZSN0FFemdxTFdlcTVvc3AwZE05NEFvRW5nOHpZWDUtZG9ORHBnT1JGc2UySDh3WWl3MU53VjFvbHRSdjdxMUlGM2Z6SmdaLTVaWWxhamJEems0Uld3MGlTT0Z0bkh5Y0hpcnY1aXptSQ&q=https%3A%2F%2Fwww.tiktok.com%2F%40aespa_official&v=ZeerrnuLi5E",
|
||||
},
|
||||
Text {
|
||||
text: "\n",
|
||||
},
|
||||
Web {
|
||||
text: "https://twitter.com/aespa_Official",
|
||||
url: "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqa3lNcG1lMHkwSzNLQVBrUXFNTXl0N1hNa04wUXxBQ3Jtc0tubm1sQkdaVjNYR04xOHpJV3NxZVBpb3I5V1FVOHVFNC1uWE5vb211ZmZKYzhTZXZfbjlkY09fanBRdHpjUkdRVGJJYS0xZ3NBNkVZQVhWSS0xVDYwRlRzQ0J3ODQxNDE0ODAxd1Q0cG5icVlNWndscw&q=https%3A%2F%2Ftwitter.com%2Faespa_Official&v=ZeerrnuLi5E",
|
||||
},
|
||||
Text {
|
||||
text: "\n",
|
||||
},
|
||||
Web {
|
||||
text: "https://www.facebook.com/aespa.official",
|
||||
url: "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbjdBNG5yVEFwU0JMNGZaLUpQZ1ZoeGgwT0xOZ3xBQ3Jtc0tuRFdFNlJNV29PMThRNWo5MHZrREZ1ZU5oZlkxVmE4ZlU5STFCZW1mUFVSdXJ3VUQxUnNVVkUzLWJQMS1uRzVjdkRCV2ZxSWJ6cFNxRVVzejY0SDltZFZPc2xwS3ZPZGIxcFZ6cndIVkMtUjVtZ054cw&q=https%3A%2F%2Fwww.facebook.com%2Faespa.official&v=ZeerrnuLi5E",
|
||||
},
|
||||
Text {
|
||||
text: "\n",
|
||||
},
|
||||
Web {
|
||||
text: "https://weibo.com/aespa",
|
||||
url: "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbEtGMHB6eXBESW92aEVLc1FybkRwQU95eTh6UXxBQ3Jtc0tuWXc5d2JsTHFYcHExdy1FTDFyUV9wdU1DSmxELUxGSGlPMzhBdFVkblRSZkNLQzRaMEJGUGhYLWp4RU40YUVwV3N3ZUpRTVVKVDRiY19zeE5RUkt2dW5aUVcxcHBRQldCOTE3YktXSXZlSFJhRWRjdw&q=https%3A%2F%2Fweibo.com%2Faespa&v=ZeerrnuLi5E",
|
||||
},
|
||||
Text {
|
||||
text: "\n\n",
|
||||
},
|
||||
Text {
|
||||
text: "#aespa",
|
||||
},
|
||||
Text {
|
||||
text: " ",
|
||||
},
|
||||
Text {
|
||||
text: "#æspa",
|
||||
},
|
||||
Text {
|
||||
text: " ",
|
||||
},
|
||||
Text {
|
||||
text: "#BlackMamba",
|
||||
},
|
||||
Text {
|
||||
text: " ",
|
||||
},
|
||||
Text {
|
||||
text: "#블랙맘바",
|
||||
},
|
||||
Text {
|
||||
text: " ",
|
||||
},
|
||||
Text {
|
||||
text: "#에스파",
|
||||
},
|
||||
Text {
|
||||
text: "\naespa 에스파 'Black Mamba' MV ℗ SM Entertainment",
|
||||
},
|
||||
],
|
||||
),
|
||||
}
|
||||
"###);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Reference in a new issue