link_preview/profiles/
youtube.rs1use scraper::Html;
2use url::Url;
3
4use crate::profiles::ProfileExt;
5use crate::LinkPreview;
6
7const YOUTUBE_IMAGE_STORAGE_DOMAIN: &str = "https://i.ytimg.com";
8
9pub struct YouTubeProfile {}
10
11impl ProfileExt for YouTubeProfile {
12 fn extract(html: &Html) -> Option<LinkPreview> {
13 let mut link_preview = LinkPreview::from(html);
14
15 if let Some(image_url) = link_preview.image_url {
16 let mut url = Url::parse(YOUTUBE_IMAGE_STORAGE_DOMAIN).ok()?;
17 url.set_path(image_url.path());
18 link_preview.image_url = Some(url);
19 }
20
21 Some(link_preview)
22 }
23
24 fn fits(url: &Url) -> bool {
25 url.host_str()
26 .is_some_and(|host| host.contains("youtube.com") || host.contains("youtu.be"))
27 }
28}
29
30#[cfg(test)]
31mod tests {
32 use std::str::from_utf8;
33
34 use scraper::Html;
35
36 use crate::tests::YOUTUBE_VIDEO_HTML;
37
38 use super::*;
39
40 #[test]
41 fn test_youtube_profile() {
42 let html = from_utf8(YOUTUBE_VIDEO_HTML).expect("Failed to parse YouTube HTML");
43 let html = Html::parse_document(html);
44
45 let url = Url::parse("https://youtu.be/61JHONRXhjs").expect("Failed to parse URL");
46 assert!(YouTubeProfile::fits(&url));
47
48 let link_preview = YouTubeProfile::extract(&html);
49 assert!(link_preview.is_some());
50
51 let preview = link_preview.unwrap();
52
53 assert_eq!(
54 preview.title,
55 Some("Google — Year in Search 2024".to_string())
56 );
57
58 assert_eq!(
59 preview.description,
60 Some("This year, we're celebrating the Breakout Searches of 2024. From iconic performances, to history-making breakthroughs, see the moments that shaped our year i...".to_string())
61 );
62
63 assert_eq!(
64 preview.image_url.map(|u| u.to_string()),
65 Some("https://i.ytimg.com/vi/61JHONRXhjs/maxresdefault.jpg".to_string())
66 );
67
68 assert_eq!(preview.domain, Some("www.youtube.com".to_string()));
69 }
70}