1pub mod error;
2pub(crate) mod favicon;
3mod icons_from_html;
4pub mod selectors;
5
6pub(crate) type Result<T, E = Error> = std::result::Result<T, E>;
7
8use error::Error;
9use favicon::InnerFavicon;
10pub use icons_from_html::*;
11
12pub use favicon::Favicon;
13use scraper::Html;
14use url::Url;
15
16pub fn get_default_favicon_url(base_url: &Url) -> std::result::Result<Url, url::ParseError> {
33 base_url.join("/favicon.ico")
34}
35
36pub fn get_default_favicon(base_url: &Url) -> Result<Favicon> {
38 Ok(Favicon {
39 href: get_default_favicon_url(base_url)?,
40 size: Default::default(),
41 type_: Default::default()
42 })
43}
44
45pub async fn get_favicons_from_url(
49 client: &reqwest::Client,
50 base_url: &Url,
51) -> Result<Vec<Favicon>> {
52 let res = client.get(base_url.clone()).send().await?;
53 if !res.status().is_success() {
54 return Err(Error::ReqwestSend { code: res.status() });
55 }
56 let html_raw = res.text().await?;
57 let html = Html::parse_document(&html_raw);
58 let icons = InnerFavicon::extract_favicons(&html)?
59 .into_iter()
60 .flat_map(|e| <Favicon as TryFrom<(&Url, InnerFavicon<'_>)>>::try_from((base_url, e)))
61 .collect::<Vec<Favicon>>();
62 if icons.is_empty() {
63 get_default_favicon(base_url).map(|r| vec![r])
64 } else {
65 Ok(icons)
66 }
67}
68
69#[cfg(feature = "blocking")]
71#[cfg_attr(docsrs, doc(cfg("blocking")))]
72pub fn get_blocking_favicons_from_url(
73 client: &reqwest::blocking::Client,
74 base_url: &Url,
75) -> Result<Vec<Favicon>> {
76 let res = client.get(base_url.clone()).send()?;
77 if !res.status().is_success() {
78 return Err(Error::ReqwestSend { code: res.status() });
79 }
80 let html_raw = res.text()?;
81 let html = Html::parse_document(&html_raw);
82 let icons = InnerFavicon::extract_favicons(&html)?
83 .into_iter()
84 .flat_map(|e| <Favicon as TryFrom<(&Url, InnerFavicon<'_>)>>::try_from((base_url, e)))
85 .collect::<Vec<Favicon>>();
86 if icons.is_empty() {
87 get_default_favicon(base_url).map(|r| vec![r])
88 } else {
89 Ok(icons)
90 }
91}