feat: add playlist extraction
- replace original base.js with dummy
This commit is contained in:
parent
5db85c05e8
commit
5b8c3d646a
30 changed files with 123935 additions and 40441 deletions
|
|
@ -28,7 +28,7 @@ use serde_with::{serde_as, DefaultOnError, DeserializeAs};
|
|||
///
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Text {
|
||||
Simple {
|
||||
|
|
@ -70,6 +70,8 @@ pub enum TextLink {
|
|||
},
|
||||
}
|
||||
|
||||
pub struct TextLinks {}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct TextLinkInternal {
|
||||
runs: Vec<TextLinkRun>,
|
||||
|
|
@ -150,6 +152,32 @@ pub enum PageType {
|
|||
Playlist,
|
||||
}
|
||||
|
||||
fn map_text_linkrun(lr: &TextLinkRun) -> Option<TextLink> {
|
||||
let text = lr.text.to_owned();
|
||||
let nav = &lr.navigation_endpoint;
|
||||
|
||||
Some(match &nav.watch_endpoint {
|
||||
Some(w) => TextLink::Video {
|
||||
title: text,
|
||||
video_id: w.video_id.to_owned(),
|
||||
},
|
||||
None => match &nav.browse_endpoint {
|
||||
Some(b) => TextLink::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,
|
||||
},
|
||||
},
|
||||
browse_id: b.browse_id.to_owned(),
|
||||
},
|
||||
None => TextLink::None { text },
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
impl<'de> DeserializeAs<'de, TextLink> for TextLink {
|
||||
fn deserialize_as<D>(deserializer: D) -> Result<TextLink, D::Error>
|
||||
where
|
||||
|
|
@ -157,43 +185,35 @@ impl<'de> DeserializeAs<'de, TextLink> for TextLink {
|
|||
{
|
||||
let link = TextLinkInternal::deserialize(deserializer)?;
|
||||
if link.runs.len() != 1 {
|
||||
return Err(serde::de::Error::invalid_length(link.runs.len(), &"1 run"));
|
||||
return Err(serde::de::Error::invalid_length(
|
||||
link.runs.len(),
|
||||
&"1 run, use TextLinks for more",
|
||||
));
|
||||
}
|
||||
|
||||
let text = link.runs[0].text.to_owned();
|
||||
let nav = &link.runs[0].navigation_endpoint;
|
||||
Ok(some_or_bail!(
|
||||
map_text_linkrun(&link.runs[0]),
|
||||
Err(serde::de::Error::custom("missing/invalid browse endpoint"))
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(match &nav.watch_endpoint {
|
||||
Some(w) => TextLink::Video {
|
||||
title: text,
|
||||
video_id: w.video_id.to_owned(),
|
||||
},
|
||||
None => match &nav.browse_endpoint {
|
||||
Some(b) => TextLink::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 Err(serde::de::Error::custom(
|
||||
"missing/invalid browse endpoint",
|
||||
))
|
||||
}
|
||||
},
|
||||
},
|
||||
browse_id: b.browse_id.to_owned(),
|
||||
},
|
||||
None => TextLink::None { text },
|
||||
},
|
||||
})
|
||||
impl<'de> DeserializeAs<'de, Vec<TextLink>> for TextLinks {
|
||||
fn deserialize_as<D>(deserializer: D) -> Result<Vec<TextLink>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let link = TextLinkInternal::deserialize(deserializer)?;
|
||||
Ok(link
|
||||
.runs
|
||||
.iter()
|
||||
.filter_map(|r| map_text_linkrun(r))
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::serializer::text::PageType;
|
||||
|
||||
use super::TextLink;
|
||||
use rstest::rstest;
|
||||
use serde::Deserialize;
|
||||
|
|
@ -247,12 +267,19 @@ mod tests {
|
|||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SLink {
|
||||
#[serde_as(as = "crate::serializer::text::TextLink")]
|
||||
ln: TextLink,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SLinks {
|
||||
#[serde_as(as = "crate::serializer::text::TextLinks")]
|
||||
ln: Vec<TextLink>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn t_link_video() {
|
||||
let test_json = r#"{
|
||||
|
|
@ -271,13 +298,14 @@ mod tests {
|
|||
}"#;
|
||||
|
||||
let res = serde_json::from_str::<SLink>(&test_json).unwrap();
|
||||
|
||||
if let TextLink::Video { title, video_id } = res.ln {
|
||||
assert_eq!(title, "DEEP");
|
||||
assert_eq!(video_id, "wZIoIgz5mbs");
|
||||
} else {
|
||||
panic!("not a video");
|
||||
insta::assert_debug_snapshot!(res, @r###"
|
||||
SLink {
|
||||
ln: Video {
|
||||
title: "DEEP",
|
||||
video_id: "wZIoIgz5mbs",
|
||||
},
|
||||
}
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -303,19 +331,15 @@ mod tests {
|
|||
}"#;
|
||||
|
||||
let res = serde_json::from_str::<SLink>(&test_json).unwrap();
|
||||
|
||||
if let TextLink::Browse {
|
||||
text,
|
||||
page_type,
|
||||
browse_id,
|
||||
} = res.ln
|
||||
{
|
||||
assert_eq!(text, "DEEP - The 1st Mini Album");
|
||||
assert_eq!(page_type, PageType::Album);
|
||||
assert_eq!(browse_id, "MPREb_TKV2ccxsj5i");
|
||||
} else {
|
||||
panic!("not a browse item");
|
||||
insta::assert_debug_snapshot!(res, @r###"
|
||||
SLink {
|
||||
ln: Browse {
|
||||
text: "DEEP - The 1st Mini Album",
|
||||
page_type: Album,
|
||||
browse_id: "MPREb_TKV2ccxsj5i",
|
||||
},
|
||||
}
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -341,19 +365,15 @@ mod tests {
|
|||
}"#;
|
||||
|
||||
let res = serde_json::from_str::<SLink>(&test_json).unwrap();
|
||||
|
||||
if let TextLink::Browse {
|
||||
text,
|
||||
page_type,
|
||||
browse_id,
|
||||
} = res.ln
|
||||
{
|
||||
assert_eq!(text, "laserluca");
|
||||
assert_eq!(page_type, PageType::Channel);
|
||||
assert_eq!(browse_id, "UCmxc6kXbU1J-0pR2F3wIx9A");
|
||||
} else {
|
||||
panic!("not a browse item");
|
||||
insta::assert_debug_snapshot!(res, @r###"
|
||||
SLink {
|
||||
ln: Browse {
|
||||
text: "laserluca",
|
||||
page_type: Channel,
|
||||
browse_id: "UCmxc6kXbU1J-0pR2F3wIx9A",
|
||||
},
|
||||
}
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -369,11 +389,72 @@ mod tests {
|
|||
}"#;
|
||||
|
||||
let res = serde_json::from_str::<SLink>(&test_json).unwrap();
|
||||
|
||||
if let TextLink::None { text } = res.ln {
|
||||
assert_eq!(text, "Hello World");
|
||||
} else {
|
||||
panic!("not none");
|
||||
insta::assert_debug_snapshot!(res, @r###"
|
||||
SLink {
|
||||
ln: None {
|
||||
text: "Hello World",
|
||||
},
|
||||
}
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn t_links_artists() {
|
||||
let test_json = r#"{
|
||||
"ln": {
|
||||
"runs": [
|
||||
{
|
||||
"text": "Roland Kaiser",
|
||||
"navigationEndpoint": {
|
||||
"clickTrackingParams": "CNAMEMn0AhgFIhMI3aq914Tn-QIVi9ARCB3w6w_p",
|
||||
"browseEndpoint": {
|
||||
"browseId": "UCtqi0viP-suK-okUQfaw8Ew",
|
||||
"browseEndpointContextSupportedConfigs": {
|
||||
"browseEndpointContextMusicConfig": {
|
||||
"pageType": "MUSIC_PAGE_TYPE_ARTIST"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "text": " & " },
|
||||
{
|
||||
"text": "Maite Kelly",
|
||||
"navigationEndpoint": {
|
||||
"clickTrackingParams": "CNAMEMn0AhgFIhMI3aq914Tn-QIVi9ARCB3w6w_p",
|
||||
"browseEndpoint": {
|
||||
"browseId": "UCY06CayCwdaOd1CnDgjy6uw",
|
||||
"browseEndpointContextSupportedConfigs": {
|
||||
"browseEndpointContextMusicConfig": {
|
||||
"pageType": "MUSIC_PAGE_TYPE_ARTIST"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}"#;
|
||||
|
||||
let res = serde_json::from_str::<SLinks>(&test_json).unwrap();
|
||||
insta::assert_debug_snapshot!(res, @r###"
|
||||
SLinks {
|
||||
ln: [
|
||||
Browse {
|
||||
text: "Roland Kaiser",
|
||||
page_type: Artist,
|
||||
browse_id: "UCtqi0viP-suK-okUQfaw8Ew",
|
||||
},
|
||||
None {
|
||||
text: " & ",
|
||||
},
|
||||
Browse {
|
||||
text: "Maite Kelly",
|
||||
page_type: Artist,
|
||||
browse_id: "UCY06CayCwdaOd1CnDgjy6uw",
|
||||
},
|
||||
],
|
||||
}
|
||||
"###);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Reference in a new issue