Compare commits

..

No commits in common. "169b70ff0689f92bd1f339ce492023bfdae41db3" and "8629454b5b89c25f13a9f4f8ea1a4d0b4efaa40b" have entirely different histories.

6 changed files with 70 additions and 210 deletions

View file

@ -1,7 +0,0 @@
repos:
- repo: https://github.com/cathiele/pre-commit-rust
rev: v0.1.0
hooks:
- id: cargo-fmt
- id: cargo-check
- id: cargo-clippy

View file

@ -13,8 +13,8 @@ use crate::serializer::{
};
use super::{
ChannelBadge, ContinuationEndpoint, ContinuationItemRenderer, Icon, Thumbnails, VideoBadge,
VideoListItem, VideoOwner,
ChannelBadge, ContentsRenderer, ContinuationEndpoint, ContinuationItemRenderer, Icon,
Thumbnails, VideoBadge, VideoListItem, VideoOwner,
};
/*
@ -348,16 +348,7 @@ pub enum EngagementPanelRenderer {
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChapterMarkersContent {
pub macro_markers_list_renderer: MacroMarkersListRenderer,
}
/// Chapter markers
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MacroMarkersListRenderer {
#[serde_as(as = "VecLogError<_>")]
pub contents: MapResult<Vec<MacroMarkersListItem>>,
pub macro_markers_list_renderer: ContentsRenderer<MacroMarkersListItem>,
}
/// Chapter marker
@ -375,6 +366,9 @@ pub struct MacroMarkersListItemRenderer {
/// Contains chapter start time in seconds
pub on_tap: MacroMarkersListItemOnTap,
pub thumbnail: Thumbnails,
/// Textual time (`1:42`)
#[serde_as(as = "Text")]
pub time_description: String,
/// Chapter title
#[serde_as(as = "Text")]
pub title: String,

View file

@ -5,9 +5,7 @@ use reqwest::Method;
use serde::Serialize;
use crate::{
model::{
Channel, ChannelId, Chapter, Comment, Language, Paginator, RecommendedVideo, VideoDetails,
},
model::{Channel, ChannelId, Comment, Language, Paginator, RecommendedVideo, VideoDetails},
serializer::MapResult,
timeago,
util::{self, TryRemove},
@ -260,26 +258,6 @@ impl MapResponse<VideoDetails> for response::VideoDetails {
response::video_details::EngagementPanelRenderer::None => {},
});
let chapters = chapter_panel
.map(|chapters| {
let mut content = chapters.macro_markers_list_renderer.contents;
warnings.append(&mut content.warnings);
content
.c
.into_iter()
.map(|item| Chapter {
title: item.macro_markers_list_item_renderer.title,
position: item
.macro_markers_list_item_renderer
.on_tap
.watch_endpoint
.start_time_seconds,
thumbnail: item.macro_markers_list_item_renderer.thumbnail.into(),
})
.collect::<Vec<_>>()
})
.unwrap_or_default();
let latest_comments_ctoken = comment_panel.and_then(|comments| {
let mut items = comments
.engagement_panel_title_header_renderer
@ -310,7 +288,6 @@ impl MapResponse<VideoDetails> for response::VideoDetails {
publish_date_txt,
is_live,
is_ccommons,
chapters,
recommended,
top_comments: Paginator::new(None, Vec::new(), comment_ctoken),
latest_comments: Paginator::new(None, Vec::new(), latest_comments_ctoken),
@ -573,9 +550,8 @@ mod tests {
url: "https://smarturl.it/aespa_BlackMamba"
- Text: "\n🐍The Debut Stage "
- Video:
text: "https://youtu.be/Ky5RT5oGg0w"
title: "https://youtu.be/Ky5RT5oGg0w"
id: Ky5RT5oGg0w
start_time: 0
- Text: "\n\n🎟️ aespa Showcase SYNK in LA! Tickets now on sale: "
- Web:
text: "https://www.ticketmaster.com/event/0A..."
@ -844,9 +820,8 @@ mod tests {
url: "https://www.twitch.tv/linustech"
- Text: "\n\nMUSIC CREDIT\n---------------------------------------------------\nIntro: Laszlo - Supernova\nVideo Link: "
- Video:
text: "https://www.youtube.com/watch?v=PKfxm..."
title: "https://www.youtube.com/watch?v=PKfxm..."
id: PKfxmFU3lWY
start_time: 0
- Text: "\niTunes Download Link: "
- Web:
text: "https://itunes.apple.com/us/album/sup..."
@ -857,9 +832,8 @@ mod tests {
url: "https://soundcloud.com/laszlomusic"
- Text: "\n\nOutro: Approaching Nirvana - Sugar High\nVideo Link: "
- Video:
text: "https://www.youtube.com/watch?v=ngsGB..."
title: "https://www.youtube.com/watch?v=ngsGB..."
id: ngsGBSCDwcI
start_time: 0
- Text: "\nListen on Spotify: "
- Web:
text: "http://spoti.fi/UxWkUw"
@ -886,74 +860,60 @@ mod tests {
url: "https://geni.us/Ps3XfE"
- Text: "\n\nCHAPTERS\n---------------------------------------------------\n"
- Video:
text: "0:00"
title: "0:00"
id: nFDBxBUfE74
start_time: 0
- Text: " Intro\n"
- Video:
text: "0:42"
title: "0:42"
id: nFDBxBUfE74
start_time: 42
- Text: " The PC Built for Super Efficiency\n"
- Video:
text: "2:41"
title: "2:41"
id: nFDBxBUfE74
start_time: 161
- Text: " Our BURIAL ENCLOSURE?!\n"
- Video:
text: "3:31"
title: "3:31"
id: nFDBxBUfE74
start_time: 211
- Text: " Our Power Solution (Thanks Jackery!)\n"
- Video:
text: "4:47"
title: "4:47"
id: nFDBxBUfE74
start_time: 287
- Text: " Diggin' Holes\n"
- Video:
text: "5:30"
title: "5:30"
id: nFDBxBUfE74
start_time: 330
- Text: " Colonoscopy?\n"
- Video:
text: "7:04"
title: "7:04"
id: nFDBxBUfE74
start_time: 424
- Text: " Diggin' like a man\n"
- Video:
text: "8:29"
title: "8:29"
id: nFDBxBUfE74
start_time: 509
- Text: " The world's worst woodsman\n"
- Video:
text: "9:03"
title: "9:03"
id: nFDBxBUfE74
start_time: 543
- Text: " Backyard cable management\n"
- Video:
text: "10:02"
title: "10:02"
id: nFDBxBUfE74
start_time: 602
- Text: " Time to bury this boy\n"
- Video:
text: "10:46"
title: "10:46"
id: nFDBxBUfE74
start_time: 646
- Text: " Solar Power Generation\n"
- Video:
text: "11:37"
title: "11:37"
id: nFDBxBUfE74
start_time: 697
- Text: " Issues\n"
- Video:
text: "12:08"
title: "12:08"
id: nFDBxBUfE74
start_time: 728
- Text: " First Play Test\n"
- Video:
text: "13:20"
title: "13:20"
id: nFDBxBUfE74
start_time: 800
- Text: " Conclusion"
"###);
@ -982,57 +942,6 @@ mod tests {
assert!(!details.is_live);
assert!(!details.is_ccommons);
insta::assert_yaml_snapshot!(details.chapters, {
"[].thumbnail" => insta::dynamic_redaction(move |value, _path| {
assert!(!value.as_slice().unwrap().is_empty());
"[ok]"
}),
}, @r###"
---
- title: Intro
position: 0
thumbnail: "[ok]"
- title: The PC Built for Super Efficiency
position: 42
thumbnail: "[ok]"
- title: Our BURIAL ENCLOSURE?!
position: 161
thumbnail: "[ok]"
- title: Our Power Solution (Thanks Jackery!)
position: 211
thumbnail: "[ok]"
- title: "Diggin' Holes"
position: 287
thumbnail: "[ok]"
- title: Colonoscopy?
position: 330
thumbnail: "[ok]"
- title: "Diggin' like a man"
position: 424
thumbnail: "[ok]"
- title: "The world's worst woodsman"
position: 509
thumbnail: "[ok]"
- title: Backyard cable management
position: 543
thumbnail: "[ok]"
- title: Time to bury this boy
position: 602
thumbnail: "[ok]"
- title: Solar Power Generation
position: 646
thumbnail: "[ok]"
- title: Issues
position: 697
thumbnail: "[ok]"
- title: First Play Test
position: 728
thumbnail: "[ok]"
- title: Conclusion
position: 800
thumbnail: "[ok]"
"###);
assert!(!details.recommended.items.is_empty());
assert!(!details.recommended.is_exhausted());
@ -1062,9 +971,8 @@ mod tests {
---
- Text: "Live NASA - Views Of Earth from Space\nLive video feed of Earth from the International Space Station (ISS) Cameras\n-----------------------------------------------------------------------------------------------------\nWatch our latest video - The Sun - 4K Video / Solar Flares\n"
- Video:
text: "https://www.youtube.com/watch?v=SEzK4..."
title: "https://www.youtube.com/watch?v=SEzK4..."
id: SEzK4ZfMvUQ
start_time: 0
- Text: "\n-----------------------------------------------------------------------------------------------------\nNasa ISS live stream from aboard the International Space Station as it circles the earth at 240 miles above the planet, on the edge of space in low earth orbit. \n\nThe station is crewed by NASA astronauts as well as Russian Cosmonauts and a mixture of Japanese, Canadian and European astronauts as well.\n\n"
- Text: " "
- Text: " "
@ -1124,7 +1032,7 @@ mod tests {
let rp = RustyPipe::builder().strict().build();
let details = rp.query().video_details("HRKu0cvrr_o").await.unwrap();
// dbg!(&details);
dbg!(&details);
assert_eq!(details.id, "HRKu0cvrr_o");
assert_eq!(

View file

@ -245,8 +245,6 @@ pub struct VideoDetails {
///
/// https://creativecommons.org/licenses/by/3.0/
pub is_ccommons: bool,
/// Chapters of the video
pub chapters: Vec<Chapter>,
/// Recommended videos
///
/// Note: Recommendations are not available for age-restricted videos
@ -257,18 +255,6 @@ pub struct VideoDetails {
pub latest_comments: Paginator<Comment>,
}
/// Videos can consist of different chapters, which YouTube shows
/// on the seek bar and below the description text.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Chapter {
/// Chapter title
pub title: String,
/// Chapter position in seconds
pub position: u32,
/// Chapter thumbnail
pub thumbnail: Vec<Thumbnail>,
}
/*
@RECOMMENDATIONS
*/

View file

@ -10,19 +10,15 @@ pub enum TextComponent {
/// Web link
Web { text: String, url: String },
/// Link to a YouTube video
Video {
text: String,
id: String,
start_time: u32,
},
Video { title: String, id: String },
/// Link to a YouTube channel
Channel { text: String, id: String },
Channel { name: String, id: String },
/// Link to a YouTube playlist
Playlist { text: String, id: String },
Playlist { name: String, id: String },
/// Link to a YouTube Music artist
Artist { text: String, id: String },
Artist { name: String, id: String },
/// Link to a YouTube Music album
Album { text: String, id: String },
Album { name: String, id: String },
}
/// Trait for converting rich text to plain text.
@ -50,27 +46,12 @@ pub trait ToHtml {
fn to_html_yt_host(&self, yt_host: &str) -> String;
}
impl TextComponent {
pub fn get_text<'a>(&'a self) -> &'a str {
impl ToPlaintext for TextComponent {
fn to_plaintext_yt_host(&self, yt_host: &str) -> String {
match self {
TextComponent::Text(text) => text,
TextComponent::Web { text, .. } => text,
TextComponent::Video { text, .. } => text,
TextComponent::Channel { text, .. } => text,
TextComponent::Playlist { text, .. } => text,
TextComponent::Artist { text, .. } => text,
TextComponent::Album { text, .. } => text,
}
}
pub fn get_url(&self, yt_host: &str) -> String {
match self {
TextComponent::Text(_) => "".to_owned(),
TextComponent::Text(text) => text.to_owned(),
TextComponent::Web { url, .. } => url.to_owned(),
TextComponent::Video { id, start_time, .. } => match start_time {
0 => format!("{}/watch?v={}", yt_host, id),
n => format!("{}/watch?v={}&t={}s", yt_host, id, n),
},
TextComponent::Video { id, .. } => format!("{}/watch?v={}", yt_host, id),
TextComponent::Channel { id, .. } | TextComponent::Artist { id, .. } => {
format!("{}/channel/{}", yt_host, id)
}
@ -81,15 +62,6 @@ impl TextComponent {
}
}
impl ToPlaintext for TextComponent {
fn to_plaintext_yt_host(&self, yt_host: &str) -> String {
match self {
TextComponent::Text(text) => text.to_owned(),
_ => self.get_url(yt_host),
}
}
}
#[cfg(feature = "html")]
impl ToHtml for TextComponent {
fn to_html_yt_host(&self, yt_host: &str) -> String {
@ -97,18 +69,35 @@ impl ToHtml for TextComponent {
TextComponent::Text(text) => askama_escape::escape(&text, askama_escape::Html)
.to_string()
.replace("\n", "<br>"),
TextComponent::Web { text, .. } => {
TextComponent::Web { text, url } => {
format!(
r#"<a href="{}" target="_blank" rel="noreferrer">{}</a>"#,
self.get_url(yt_host),
askama_escape::escape(text, askama_escape::Html)
url,
askama_escape::escape(&text, askama_escape::Html)
)
}
_ => {
TextComponent::Video { title, id } => {
format!(
r#"<a href="{}">{}</a>"#,
self.get_url(yt_host),
askama_escape::escape(self.get_text(), askama_escape::Html)
r#"<a href="{}/watch?v={}" rel="noreferrer">{}</a>"#,
yt_host,
id,
askama_escape::escape(&title, askama_escape::Html)
)
}
TextComponent::Channel { name, id } | TextComponent::Artist { name, id } => {
format!(
r#"<a href="{}/channel/{}" rel="noreferrer">{}</a>"#,
yt_host,
id,
askama_escape::escape(&name, askama_escape::Html)
)
}
TextComponent::Playlist { name, id } | TextComponent::Album { name, id } => {
format!(
r#"<a href="{}/playlist?list={}" rel="noreferrer">{}</a>"#,
yt_host,
id,
askama_escape::escape(&name, askama_escape::Html)
)
}
}
@ -144,7 +133,7 @@ mod tests {
text::TextComponent::Text { text: "🎧Listen and download aespa's debut single \"Black Mamba\": ".to_owned() },
text::TextComponent::Web { text: "https://smarturl.it/aespa_BlackMamba".to_owned(), url: "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbFY1QmpQamJPSms0Z1FnVTlQUS00ZFhBZnBJZ3xBQ3Jtc0tuRGJBanludGoyRnphb2dZWVd3cUNnS3dEd0FnNHFOZEY1NHBJaHFmLXpaWUJwX3ZucDZxVnpGeHNGX1FpMzFkZW9jQkI2Mi1wNGJ1UVFNN3h1MnN3R3JLMzdxU01nZ01POHBGcmxHU2puSUk1WHRzQQ&q=https%3A%2F%2Fsmarturl.it%2Faespa_BlackMamba&v=ZeerrnuLi5E".to_owned() },
text::TextComponent::Text { text: "\n🐍The Debut Stage ".to_owned() },
text::TextComponent::Video { text: "https://youtu.be/Ky5RT5oGg0w".to_owned(), video_id: "Ky5RT5oGg0w".to_owned(), start_time: 0 },
text::TextComponent::Video { title: "https://youtu.be/Ky5RT5oGg0w".to_owned(), video_id: "Ky5RT5oGg0w".to_owned() },
text::TextComponent::Text { text: "\n\n🎟️ aespa Showcase SYNK in LA! Tickets now on sale: ".to_owned() },
text::TextComponent::Web { text: "https://www.ticketmaster.com/event/0A...".to_owned(), url: "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbFpUMEZiaXJWWkszaVZXaEM0emxWU1JQV3NoQXxBQ3Jtc0tuU2g4VWNPNE5UY3hoSWYtamFzX0h4bUVQLVJiRy1ubDZrTnh3MUpGdDNSaUo0ZlMyT3lUM28ycUVBdHJLMndGcDhla3BkOFpxSVFfOS1QdVJPVHBUTEV1LXpOV0J2QXdhV05lV210cEJtZUJMeHdaTQ&q=https%3A%2F%2Fwww.ticketmaster.com%2Fevent%2F0A005CCD9E871F6E&v=ZeerrnuLi5E".to_owned() },
text::TextComponent::Text { text: "\n\nSubscribe to aespa Official YouTube Channel!\n".to_owned() },
@ -209,7 +198,7 @@ aespa 에스파 'Black Mamba' MV ℗ SM Entertainment"#
let html = richtext.to_html_yt_host("https://piped.kavin.rocks");
assert_eq!(
html,
"🎧Listen and download aespa&#x27;s debut single &quot;Black Mamba&quot;: <a href=\"https://smarturl.it/aespa_BlackMamba\" target=\"_blank\" rel=\"noreferrer\">https://smarturl.it/aespa_BlackMamba</a><br>🐍The Debut Stage <a href=\"https://piped.kavin.rocks/watch?v=Ky5RT5oGg0w\">https://youtu.be/Ky5RT5oGg0w</a><br><br>🎟\u{fe0f} aespa Showcase SYNK in LA! Tickets now on sale: <a href=\"https://www.ticketmaster.com/event/0A005CCD9E871F6E\" target=\"_blank\" rel=\"noreferrer\">https://www.ticketmaster.com/event/0A...</a><br><br>Subscribe to aespa Official YouTube Channel!<br><a href=\"https://www.youtube.com/aespa?sub_confirmation=1\" target=\"_blank\" rel=\"noreferrer\">https://www.youtube.com/aespa?sub_con...</a><br><br>aespa official<br><a href=\"https://www.youtube.com/c/aespa\" target=\"_blank\" rel=\"noreferrer\">https://www.youtube.com/c/aespa</a><br><a href=\"https://www.instagram.com/aespa_official\" target=\"_blank\" rel=\"noreferrer\">https://www.instagram.com/aespa_official</a><br><a href=\"https://www.tiktok.com/@aespa_official\" target=\"_blank\" rel=\"noreferrer\">https://www.tiktok.com/@aespa_official</a><br><a href=\"https://twitter.com/aespa_Official\" target=\"_blank\" rel=\"noreferrer\">https://twitter.com/aespa_Official</a><br><a href=\"https://www.facebook.com/aespa.official\" target=\"_blank\" rel=\"noreferrer\">https://www.facebook.com/aespa.official</a><br><a href=\"https://weibo.com/aespa\" target=\"_blank\" rel=\"noreferrer\">https://weibo.com/aespa</a><br><br>#aespa #æspa #BlackMamba #블랙맘바 #에스파<br>aespa 에스파 &#x27;Black Mamba&#x27; MV ℗ SM Entertainment"
"🎧Listen and download aespa&#x27;s debut single &quot;Black Mamba&quot;: <a href=\"https://smarturl.it/aespa_BlackMamba\" target=\"_blank\" rel=\"noreferrer\">https://smarturl.it/aespa_BlackMamba</a><br>🐍The Debut Stage <a href=\"https://piped.kavin.rocks/watch?v=Ky5RT5oGg0w\" rel=\"noreferrer\">https://youtu.be/Ky5RT5oGg0w</a><br><br>🎟\u{fe0f} aespa Showcase SYNK in LA! Tickets now on sale: <a href=\"https://www.ticketmaster.com/event/0A005CCD9E871F6E\" target=\"_blank\" rel=\"noreferrer\">https://www.ticketmaster.com/event/0A...</a><br><br>Subscribe to aespa Official YouTube Channel!<br><a href=\"https://www.youtube.com/aespa?sub_confirmation=1\" target=\"_blank\" rel=\"noreferrer\">https://www.youtube.com/aespa?sub_con...</a><br><br>aespa official<br><a href=\"https://www.youtube.com/c/aespa\" target=\"_blank\" rel=\"noreferrer\">https://www.youtube.com/c/aespa</a><br><a href=\"https://www.instagram.com/aespa_official\" target=\"_blank\" rel=\"noreferrer\">https://www.instagram.com/aespa_official</a><br><a href=\"https://www.tiktok.com/@aespa_official\" target=\"_blank\" rel=\"noreferrer\">https://www.tiktok.com/@aespa_official</a><br><a href=\"https://twitter.com/aespa_Official\" target=\"_blank\" rel=\"noreferrer\">https://twitter.com/aespa_Official</a><br><a href=\"https://www.facebook.com/aespa.official\" target=\"_blank\" rel=\"noreferrer\">https://www.facebook.com/aespa.official</a><br><a href=\"https://weibo.com/aespa\" target=\"_blank\" rel=\"noreferrer\">https://weibo.com/aespa</a><br><br>#aespa #æspa #BlackMamba #블랙맘바 #에스파<br>aespa 에스파 &#x27;Black Mamba&#x27; MV ℗ SM Entertainment"
);
}
}

View file

@ -86,9 +86,8 @@ pub struct TextComponents(pub Vec<TextComponent>);
#[derive(Debug, Clone)]
pub enum TextComponent {
Video {
text: String,
title: String,
video_id: String,
start_time: u32,
},
Browse {
text: String,
@ -142,8 +141,6 @@ struct NavigationEndpoint {
#[serde(rename_all = "camelCase")]
struct WatchEndpoint {
video_id: String,
#[serde(default)]
start_time_seconds: u32,
}
#[derive(Deserialize)]
@ -205,9 +202,8 @@ fn map_richtext_run(lr: &RichTextRun) -> Option<TextComponent> {
Some(match &nav.watch_endpoint {
Some(w) => TextComponent::Video {
text,
title: text,
video_id: w.video_id.to_owned(),
start_time: w.start_time_seconds,
},
None => match &nav.browse_endpoint {
Some(b) => TextComponent::Browse {
@ -288,14 +284,9 @@ impl TryFrom<TextComponent> for crate::model::ChannelId {
impl From<TextComponent> for crate::model::richtext::TextComponent {
fn from(component: TextComponent) -> Self {
match component {
TextComponent::Video {
text,
video_id,
start_time,
} => Self::Video {
text,
TextComponent::Video { title, video_id } => Self::Video {
title,
id: video_id,
start_time,
},
TextComponent::Browse {
text,
@ -303,19 +294,19 @@ impl From<TextComponent> for crate::model::richtext::TextComponent {
browse_id,
} => match page_type {
PageType::Artist => Self::Artist {
text,
name: text,
id: browse_id,
},
PageType::Album => Self::Album {
text,
name: text,
id: browse_id,
},
PageType::Channel => Self::Channel {
text,
name: text,
id: browse_id,
},
PageType::Playlist => Self::Playlist {
text,
name: text,
id: browse_id,
},
},
@ -452,9 +443,8 @@ mod tests {
insta::assert_debug_snapshot!(res, @r###"
SLink {
ln: Video {
text: "DEEP",
title: "DEEP",
video_id: "wZIoIgz5mbs",
start_time: 0,
},
}
"###);