Compare commits
3 commits
8629454b5b
...
169b70ff06
Author | SHA1 | Date | |
---|---|---|---|
169b70ff06 | |||
e4b10fcc83 | |||
ed522e622d |
6 changed files with 210 additions and 70 deletions
7
.pre-commit-config.yaml
Normal file
7
.pre-commit-config.yaml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/cathiele/pre-commit-rust
|
||||||
|
rev: v0.1.0
|
||||||
|
hooks:
|
||||||
|
- id: cargo-fmt
|
||||||
|
- id: cargo-check
|
||||||
|
- id: cargo-clippy
|
|
@ -13,8 +13,8 @@ use crate::serializer::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
ChannelBadge, ContentsRenderer, ContinuationEndpoint, ContinuationItemRenderer, Icon,
|
ChannelBadge, ContinuationEndpoint, ContinuationItemRenderer, Icon, Thumbnails, VideoBadge,
|
||||||
Thumbnails, VideoBadge, VideoListItem, VideoOwner,
|
VideoListItem, VideoOwner,
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -348,7 +348,16 @@ pub enum EngagementPanelRenderer {
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ChapterMarkersContent {
|
pub struct ChapterMarkersContent {
|
||||||
pub macro_markers_list_renderer: ContentsRenderer<MacroMarkersListItem>,
|
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>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Chapter marker
|
/// Chapter marker
|
||||||
|
@ -366,9 +375,6 @@ pub struct MacroMarkersListItemRenderer {
|
||||||
/// Contains chapter start time in seconds
|
/// Contains chapter start time in seconds
|
||||||
pub on_tap: MacroMarkersListItemOnTap,
|
pub on_tap: MacroMarkersListItemOnTap,
|
||||||
pub thumbnail: Thumbnails,
|
pub thumbnail: Thumbnails,
|
||||||
/// Textual time (`1:42`)
|
|
||||||
#[serde_as(as = "Text")]
|
|
||||||
pub time_description: String,
|
|
||||||
/// Chapter title
|
/// Chapter title
|
||||||
#[serde_as(as = "Text")]
|
#[serde_as(as = "Text")]
|
||||||
pub title: String,
|
pub title: String,
|
||||||
|
|
|
@ -5,7 +5,9 @@ use reqwest::Method;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
model::{Channel, ChannelId, Comment, Language, Paginator, RecommendedVideo, VideoDetails},
|
model::{
|
||||||
|
Channel, ChannelId, Chapter, Comment, Language, Paginator, RecommendedVideo, VideoDetails,
|
||||||
|
},
|
||||||
serializer::MapResult,
|
serializer::MapResult,
|
||||||
timeago,
|
timeago,
|
||||||
util::{self, TryRemove},
|
util::{self, TryRemove},
|
||||||
|
@ -258,6 +260,26 @@ impl MapResponse<VideoDetails> for response::VideoDetails {
|
||||||
response::video_details::EngagementPanelRenderer::None => {},
|
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 latest_comments_ctoken = comment_panel.and_then(|comments| {
|
||||||
let mut items = comments
|
let mut items = comments
|
||||||
.engagement_panel_title_header_renderer
|
.engagement_panel_title_header_renderer
|
||||||
|
@ -288,6 +310,7 @@ impl MapResponse<VideoDetails> for response::VideoDetails {
|
||||||
publish_date_txt,
|
publish_date_txt,
|
||||||
is_live,
|
is_live,
|
||||||
is_ccommons,
|
is_ccommons,
|
||||||
|
chapters,
|
||||||
recommended,
|
recommended,
|
||||||
top_comments: Paginator::new(None, Vec::new(), comment_ctoken),
|
top_comments: Paginator::new(None, Vec::new(), comment_ctoken),
|
||||||
latest_comments: Paginator::new(None, Vec::new(), latest_comments_ctoken),
|
latest_comments: Paginator::new(None, Vec::new(), latest_comments_ctoken),
|
||||||
|
@ -550,8 +573,9 @@ mod tests {
|
||||||
url: "https://smarturl.it/aespa_BlackMamba"
|
url: "https://smarturl.it/aespa_BlackMamba"
|
||||||
- Text: "\n🐍The Debut Stage "
|
- Text: "\n🐍The Debut Stage "
|
||||||
- Video:
|
- Video:
|
||||||
title: "https://youtu.be/Ky5RT5oGg0w"
|
text: "https://youtu.be/Ky5RT5oGg0w"
|
||||||
id: Ky5RT5oGg0w
|
id: Ky5RT5oGg0w
|
||||||
|
start_time: 0
|
||||||
- Text: "\n\n🎟️ aespa Showcase SYNK in LA! Tickets now on sale: "
|
- Text: "\n\n🎟️ aespa Showcase SYNK in LA! Tickets now on sale: "
|
||||||
- Web:
|
- Web:
|
||||||
text: "https://www.ticketmaster.com/event/0A..."
|
text: "https://www.ticketmaster.com/event/0A..."
|
||||||
|
@ -820,8 +844,9 @@ mod tests {
|
||||||
url: "https://www.twitch.tv/linustech"
|
url: "https://www.twitch.tv/linustech"
|
||||||
- Text: "\n\nMUSIC CREDIT\n---------------------------------------------------\nIntro: Laszlo - Supernova\nVideo Link: "
|
- Text: "\n\nMUSIC CREDIT\n---------------------------------------------------\nIntro: Laszlo - Supernova\nVideo Link: "
|
||||||
- Video:
|
- Video:
|
||||||
title: "https://www.youtube.com/watch?v=PKfxm..."
|
text: "https://www.youtube.com/watch?v=PKfxm..."
|
||||||
id: PKfxmFU3lWY
|
id: PKfxmFU3lWY
|
||||||
|
start_time: 0
|
||||||
- Text: "\niTunes Download Link: "
|
- Text: "\niTunes Download Link: "
|
||||||
- Web:
|
- Web:
|
||||||
text: "https://itunes.apple.com/us/album/sup..."
|
text: "https://itunes.apple.com/us/album/sup..."
|
||||||
|
@ -832,8 +857,9 @@ mod tests {
|
||||||
url: "https://soundcloud.com/laszlomusic"
|
url: "https://soundcloud.com/laszlomusic"
|
||||||
- Text: "\n\nOutro: Approaching Nirvana - Sugar High\nVideo Link: "
|
- Text: "\n\nOutro: Approaching Nirvana - Sugar High\nVideo Link: "
|
||||||
- Video:
|
- Video:
|
||||||
title: "https://www.youtube.com/watch?v=ngsGB..."
|
text: "https://www.youtube.com/watch?v=ngsGB..."
|
||||||
id: ngsGBSCDwcI
|
id: ngsGBSCDwcI
|
||||||
|
start_time: 0
|
||||||
- Text: "\nListen on Spotify: "
|
- Text: "\nListen on Spotify: "
|
||||||
- Web:
|
- Web:
|
||||||
text: "http://spoti.fi/UxWkUw"
|
text: "http://spoti.fi/UxWkUw"
|
||||||
|
@ -860,60 +886,74 @@ mod tests {
|
||||||
url: "https://geni.us/Ps3XfE"
|
url: "https://geni.us/Ps3XfE"
|
||||||
- Text: "\n\nCHAPTERS\n---------------------------------------------------\n"
|
- Text: "\n\nCHAPTERS\n---------------------------------------------------\n"
|
||||||
- Video:
|
- Video:
|
||||||
title: "0:00"
|
text: "0:00"
|
||||||
id: nFDBxBUfE74
|
id: nFDBxBUfE74
|
||||||
|
start_time: 0
|
||||||
- Text: " Intro\n"
|
- Text: " Intro\n"
|
||||||
- Video:
|
- Video:
|
||||||
title: "0:42"
|
text: "0:42"
|
||||||
id: nFDBxBUfE74
|
id: nFDBxBUfE74
|
||||||
|
start_time: 42
|
||||||
- Text: " The PC Built for Super Efficiency\n"
|
- Text: " The PC Built for Super Efficiency\n"
|
||||||
- Video:
|
- Video:
|
||||||
title: "2:41"
|
text: "2:41"
|
||||||
id: nFDBxBUfE74
|
id: nFDBxBUfE74
|
||||||
|
start_time: 161
|
||||||
- Text: " Our BURIAL ENCLOSURE?!\n"
|
- Text: " Our BURIAL ENCLOSURE?!\n"
|
||||||
- Video:
|
- Video:
|
||||||
title: "3:31"
|
text: "3:31"
|
||||||
id: nFDBxBUfE74
|
id: nFDBxBUfE74
|
||||||
|
start_time: 211
|
||||||
- Text: " Our Power Solution (Thanks Jackery!)\n"
|
- Text: " Our Power Solution (Thanks Jackery!)\n"
|
||||||
- Video:
|
- Video:
|
||||||
title: "4:47"
|
text: "4:47"
|
||||||
id: nFDBxBUfE74
|
id: nFDBxBUfE74
|
||||||
|
start_time: 287
|
||||||
- Text: " Diggin' Holes\n"
|
- Text: " Diggin' Holes\n"
|
||||||
- Video:
|
- Video:
|
||||||
title: "5:30"
|
text: "5:30"
|
||||||
id: nFDBxBUfE74
|
id: nFDBxBUfE74
|
||||||
|
start_time: 330
|
||||||
- Text: " Colonoscopy?\n"
|
- Text: " Colonoscopy?\n"
|
||||||
- Video:
|
- Video:
|
||||||
title: "7:04"
|
text: "7:04"
|
||||||
id: nFDBxBUfE74
|
id: nFDBxBUfE74
|
||||||
|
start_time: 424
|
||||||
- Text: " Diggin' like a man\n"
|
- Text: " Diggin' like a man\n"
|
||||||
- Video:
|
- Video:
|
||||||
title: "8:29"
|
text: "8:29"
|
||||||
id: nFDBxBUfE74
|
id: nFDBxBUfE74
|
||||||
|
start_time: 509
|
||||||
- Text: " The world's worst woodsman\n"
|
- Text: " The world's worst woodsman\n"
|
||||||
- Video:
|
- Video:
|
||||||
title: "9:03"
|
text: "9:03"
|
||||||
id: nFDBxBUfE74
|
id: nFDBxBUfE74
|
||||||
|
start_time: 543
|
||||||
- Text: " Backyard cable management\n"
|
- Text: " Backyard cable management\n"
|
||||||
- Video:
|
- Video:
|
||||||
title: "10:02"
|
text: "10:02"
|
||||||
id: nFDBxBUfE74
|
id: nFDBxBUfE74
|
||||||
|
start_time: 602
|
||||||
- Text: " Time to bury this boy\n"
|
- Text: " Time to bury this boy\n"
|
||||||
- Video:
|
- Video:
|
||||||
title: "10:46"
|
text: "10:46"
|
||||||
id: nFDBxBUfE74
|
id: nFDBxBUfE74
|
||||||
|
start_time: 646
|
||||||
- Text: " Solar Power Generation\n"
|
- Text: " Solar Power Generation\n"
|
||||||
- Video:
|
- Video:
|
||||||
title: "11:37"
|
text: "11:37"
|
||||||
id: nFDBxBUfE74
|
id: nFDBxBUfE74
|
||||||
|
start_time: 697
|
||||||
- Text: " Issues\n"
|
- Text: " Issues\n"
|
||||||
- Video:
|
- Video:
|
||||||
title: "12:08"
|
text: "12:08"
|
||||||
id: nFDBxBUfE74
|
id: nFDBxBUfE74
|
||||||
|
start_time: 728
|
||||||
- Text: " First Play Test\n"
|
- Text: " First Play Test\n"
|
||||||
- Video:
|
- Video:
|
||||||
title: "13:20"
|
text: "13:20"
|
||||||
id: nFDBxBUfE74
|
id: nFDBxBUfE74
|
||||||
|
start_time: 800
|
||||||
- Text: " Conclusion"
|
- Text: " Conclusion"
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
|
@ -942,6 +982,57 @@ mod tests {
|
||||||
assert!(!details.is_live);
|
assert!(!details.is_live);
|
||||||
assert!(!details.is_ccommons);
|
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.items.is_empty());
|
||||||
assert!(!details.recommended.is_exhausted());
|
assert!(!details.recommended.is_exhausted());
|
||||||
|
|
||||||
|
@ -971,8 +1062,9 @@ 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"
|
- 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:
|
- Video:
|
||||||
title: "https://www.youtube.com/watch?v=SEzK4..."
|
text: "https://www.youtube.com/watch?v=SEzK4..."
|
||||||
id: SEzK4ZfMvUQ
|
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: "\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: " "
|
||||||
- Text: " "
|
- Text: " "
|
||||||
|
@ -1032,7 +1124,7 @@ mod tests {
|
||||||
let rp = RustyPipe::builder().strict().build();
|
let rp = RustyPipe::builder().strict().build();
|
||||||
let details = rp.query().video_details("HRKu0cvrr_o").await.unwrap();
|
let details = rp.query().video_details("HRKu0cvrr_o").await.unwrap();
|
||||||
|
|
||||||
dbg!(&details);
|
// dbg!(&details);
|
||||||
|
|
||||||
assert_eq!(details.id, "HRKu0cvrr_o");
|
assert_eq!(details.id, "HRKu0cvrr_o");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
@ -245,6 +245,8 @@ pub struct VideoDetails {
|
||||||
///
|
///
|
||||||
/// https://creativecommons.org/licenses/by/3.0/
|
/// https://creativecommons.org/licenses/by/3.0/
|
||||||
pub is_ccommons: bool,
|
pub is_ccommons: bool,
|
||||||
|
/// Chapters of the video
|
||||||
|
pub chapters: Vec<Chapter>,
|
||||||
/// Recommended videos
|
/// Recommended videos
|
||||||
///
|
///
|
||||||
/// Note: Recommendations are not available for age-restricted videos
|
/// Note: Recommendations are not available for age-restricted videos
|
||||||
|
@ -255,6 +257,18 @@ pub struct VideoDetails {
|
||||||
pub latest_comments: Paginator<Comment>,
|
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
|
@RECOMMENDATIONS
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -10,15 +10,19 @@ pub enum TextComponent {
|
||||||
/// Web link
|
/// Web link
|
||||||
Web { text: String, url: String },
|
Web { text: String, url: String },
|
||||||
/// Link to a YouTube video
|
/// Link to a YouTube video
|
||||||
Video { title: String, id: String },
|
Video {
|
||||||
|
text: String,
|
||||||
|
id: String,
|
||||||
|
start_time: u32,
|
||||||
|
},
|
||||||
/// Link to a YouTube channel
|
/// Link to a YouTube channel
|
||||||
Channel { name: String, id: String },
|
Channel { text: String, id: String },
|
||||||
/// Link to a YouTube playlist
|
/// Link to a YouTube playlist
|
||||||
Playlist { name: String, id: String },
|
Playlist { text: String, id: String },
|
||||||
/// Link to a YouTube Music artist
|
/// Link to a YouTube Music artist
|
||||||
Artist { name: String, id: String },
|
Artist { text: String, id: String },
|
||||||
/// Link to a YouTube Music album
|
/// Link to a YouTube Music album
|
||||||
Album { name: String, id: String },
|
Album { text: String, id: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait for converting rich text to plain text.
|
/// Trait for converting rich text to plain text.
|
||||||
|
@ -46,12 +50,27 @@ pub trait ToHtml {
|
||||||
fn to_html_yt_host(&self, yt_host: &str) -> String;
|
fn to_html_yt_host(&self, yt_host: &str) -> String;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToPlaintext for TextComponent {
|
impl TextComponent {
|
||||||
fn to_plaintext_yt_host(&self, yt_host: &str) -> String {
|
pub fn get_text<'a>(&'a self) -> &'a str {
|
||||||
match self {
|
match self {
|
||||||
TextComponent::Text(text) => text.to_owned(),
|
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::Web { url, .. } => url.to_owned(),
|
TextComponent::Web { url, .. } => url.to_owned(),
|
||||||
TextComponent::Video { id, .. } => format!("{}/watch?v={}", yt_host, id),
|
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::Channel { id, .. } | TextComponent::Artist { id, .. } => {
|
TextComponent::Channel { id, .. } | TextComponent::Artist { id, .. } => {
|
||||||
format!("{}/channel/{}", yt_host, id)
|
format!("{}/channel/{}", yt_host, id)
|
||||||
}
|
}
|
||||||
|
@ -62,6 +81,15 @@ impl ToPlaintext for 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")]
|
#[cfg(feature = "html")]
|
||||||
impl ToHtml for TextComponent {
|
impl ToHtml for TextComponent {
|
||||||
fn to_html_yt_host(&self, yt_host: &str) -> String {
|
fn to_html_yt_host(&self, yt_host: &str) -> String {
|
||||||
|
@ -69,35 +97,18 @@ impl ToHtml for TextComponent {
|
||||||
TextComponent::Text(text) => askama_escape::escape(&text, askama_escape::Html)
|
TextComponent::Text(text) => askama_escape::escape(&text, askama_escape::Html)
|
||||||
.to_string()
|
.to_string()
|
||||||
.replace("\n", "<br>"),
|
.replace("\n", "<br>"),
|
||||||
TextComponent::Web { text, url } => {
|
TextComponent::Web { text, .. } => {
|
||||||
format!(
|
format!(
|
||||||
r#"<a href="{}" target="_blank" rel="noreferrer">{}</a>"#,
|
r#"<a href="{}" target="_blank" rel="noreferrer">{}</a>"#,
|
||||||
url,
|
self.get_url(yt_host),
|
||||||
askama_escape::escape(&text, askama_escape::Html)
|
askama_escape::escape(text, askama_escape::Html)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
TextComponent::Video { title, id } => {
|
_ => {
|
||||||
format!(
|
format!(
|
||||||
r#"<a href="{}/watch?v={}" rel="noreferrer">{}</a>"#,
|
r#"<a href="{}">{}</a>"#,
|
||||||
yt_host,
|
self.get_url(yt_host),
|
||||||
id,
|
askama_escape::escape(self.get_text(), askama_escape::Html)
|
||||||
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)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,7 +144,7 @@ mod tests {
|
||||||
text::TextComponent::Text { text: "🎧Listen and download aespa's debut single \"Black Mamba\": ".to_owned() },
|
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::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::Text { text: "\n🐍The Debut Stage ".to_owned() },
|
||||||
text::TextComponent::Video { title: "https://youtu.be/Ky5RT5oGg0w".to_owned(), video_id: "Ky5RT5oGg0w".to_owned() },
|
text::TextComponent::Video { text: "https://youtu.be/Ky5RT5oGg0w".to_owned(), video_id: "Ky5RT5oGg0w".to_owned(), start_time: 0 },
|
||||||
text::TextComponent::Text { text: "\n\n🎟️ aespa Showcase SYNK in LA! Tickets now on sale: ".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::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() },
|
text::TextComponent::Text { text: "\n\nSubscribe to aespa Official YouTube Channel!\n".to_owned() },
|
||||||
|
@ -198,7 +209,7 @@ aespa 에스파 'Black Mamba' MV ℗ SM Entertainment"#
|
||||||
let html = richtext.to_html_yt_host("https://piped.kavin.rocks");
|
let html = richtext.to_html_yt_host("https://piped.kavin.rocks");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
html,
|
html,
|
||||||
"🎧Listen and download aespa's debut single "Black Mamba": <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 에스파 'Black Mamba' MV ℗ SM Entertainment"
|
"🎧Listen and download aespa's debut single "Black Mamba": <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 에스파 'Black Mamba' MV ℗ SM Entertainment"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,8 +86,9 @@ pub struct TextComponents(pub Vec<TextComponent>);
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum TextComponent {
|
pub enum TextComponent {
|
||||||
Video {
|
Video {
|
||||||
title: String,
|
text: String,
|
||||||
video_id: String,
|
video_id: String,
|
||||||
|
start_time: u32,
|
||||||
},
|
},
|
||||||
Browse {
|
Browse {
|
||||||
text: String,
|
text: String,
|
||||||
|
@ -141,6 +142,8 @@ struct NavigationEndpoint {
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct WatchEndpoint {
|
struct WatchEndpoint {
|
||||||
video_id: String,
|
video_id: String,
|
||||||
|
#[serde(default)]
|
||||||
|
start_time_seconds: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
@ -202,8 +205,9 @@ fn map_richtext_run(lr: &RichTextRun) -> Option<TextComponent> {
|
||||||
|
|
||||||
Some(match &nav.watch_endpoint {
|
Some(match &nav.watch_endpoint {
|
||||||
Some(w) => TextComponent::Video {
|
Some(w) => TextComponent::Video {
|
||||||
title: text,
|
text,
|
||||||
video_id: w.video_id.to_owned(),
|
video_id: w.video_id.to_owned(),
|
||||||
|
start_time: w.start_time_seconds,
|
||||||
},
|
},
|
||||||
None => match &nav.browse_endpoint {
|
None => match &nav.browse_endpoint {
|
||||||
Some(b) => TextComponent::Browse {
|
Some(b) => TextComponent::Browse {
|
||||||
|
@ -284,9 +288,14 @@ impl TryFrom<TextComponent> for crate::model::ChannelId {
|
||||||
impl From<TextComponent> for crate::model::richtext::TextComponent {
|
impl From<TextComponent> for crate::model::richtext::TextComponent {
|
||||||
fn from(component: TextComponent) -> Self {
|
fn from(component: TextComponent) -> Self {
|
||||||
match component {
|
match component {
|
||||||
TextComponent::Video { title, video_id } => Self::Video {
|
TextComponent::Video {
|
||||||
title,
|
text,
|
||||||
|
video_id,
|
||||||
|
start_time,
|
||||||
|
} => Self::Video {
|
||||||
|
text,
|
||||||
id: video_id,
|
id: video_id,
|
||||||
|
start_time,
|
||||||
},
|
},
|
||||||
TextComponent::Browse {
|
TextComponent::Browse {
|
||||||
text,
|
text,
|
||||||
|
@ -294,19 +303,19 @@ impl From<TextComponent> for crate::model::richtext::TextComponent {
|
||||||
browse_id,
|
browse_id,
|
||||||
} => match page_type {
|
} => match page_type {
|
||||||
PageType::Artist => Self::Artist {
|
PageType::Artist => Self::Artist {
|
||||||
name: text,
|
text,
|
||||||
id: browse_id,
|
id: browse_id,
|
||||||
},
|
},
|
||||||
PageType::Album => Self::Album {
|
PageType::Album => Self::Album {
|
||||||
name: text,
|
text,
|
||||||
id: browse_id,
|
id: browse_id,
|
||||||
},
|
},
|
||||||
PageType::Channel => Self::Channel {
|
PageType::Channel => Self::Channel {
|
||||||
name: text,
|
text,
|
||||||
id: browse_id,
|
id: browse_id,
|
||||||
},
|
},
|
||||||
PageType::Playlist => Self::Playlist {
|
PageType::Playlist => Self::Playlist {
|
||||||
name: text,
|
text,
|
||||||
id: browse_id,
|
id: browse_id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -443,8 +452,9 @@ mod tests {
|
||||||
insta::assert_debug_snapshot!(res, @r###"
|
insta::assert_debug_snapshot!(res, @r###"
|
||||||
SLink {
|
SLink {
|
||||||
ln: Video {
|
ln: Video {
|
||||||
title: "DEEP",
|
text: "DEEP",
|
||||||
video_id: "wZIoIgz5mbs",
|
video_id: "wZIoIgz5mbs",
|
||||||
|
start_time: 0,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
"###);
|
"###);
|
||||||
|
|
Loading…
Add table
Reference in a new issue