A site icon that is missing is not used (#79)
site_icon took the icon a site's page names in its <link> tags without asking for it, so a dead one was stored and /favicon.ico never tried. antirez.com names /images/favicon.png, which is a 404, while its /favicon.ico is there; the feed showed no artwork, and since the lookup happens once, never would. The named icon now has to answer with an image, as /favicon.ico already did. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- A feed whose website names an icon that is missing shows the site's `/favicon.ico` instead of
|
||||
no artwork.
|
||||
- The read button shows whether an item is read, as the pin beside it shows whether it is
|
||||
pinned: a tick when read, an envelope when not. It used to show the opposite.
|
||||
- In Directory and Popular, Subscribe is a plus again, not the tick that marks a feed you have.
|
||||
|
||||
25
src/feed.rs
25
src/feed.rs
@@ -377,22 +377,31 @@ pub async fn site_icon(client: &reqwest::Client, site: &str) -> Option<String> {
|
||||
// Relative to where the page ended up, not where it was asked for: a site that redirects
|
||||
// to /en/ would otherwise have its icon looked for in the wrong place.
|
||||
let base = resp.url().clone();
|
||||
// The icon a page names can be gone: antirez.com names /images/favicon.png, a 404, while its
|
||||
// /favicon.ico is there. Stored unchecked, it was a broken image that was never looked up again.
|
||||
if resp.status().is_success()
|
||||
&& let Ok(page) = resp.bytes().await
|
||||
&& let Some(href) = page_icon(&page)
|
||||
&& let Ok(url) = base.join(&href)
|
||||
&& is_image(client, &url).await
|
||||
{
|
||||
return Some(url.into());
|
||||
}
|
||||
let ico = base.join("/favicon.ico").ok()?;
|
||||
let resp = client.get(ico.clone()).timeout(timeout).send().await.ok()?;
|
||||
let is_image = resp
|
||||
.headers()
|
||||
.get(reqwest::header::CONTENT_TYPE)
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.is_some_and(|t| t.starts_with("image/"));
|
||||
// A site with no favicon often answers 200 with its home page, which is not an icon.
|
||||
(resp.status().is_success() && is_image).then(|| ico.into())
|
||||
is_image(client, &ico).await.then(|| ico.into())
|
||||
}
|
||||
|
||||
/// Whether `url` answers with an image. A site with no favicon often answers 200 with its home
|
||||
/// page, which is not an icon.
|
||||
async fn is_image(client: &reqwest::Client, url: &reqwest::Url) -> bool {
|
||||
let timeout = std::time::Duration::from_secs(20);
|
||||
let Ok(resp) = client.get(url.clone()).timeout(timeout).send().await else { return false };
|
||||
resp.status().is_success()
|
||||
&& resp
|
||||
.headers()
|
||||
.get(reqwest::header::CONTENT_TYPE)
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.is_some_and(|t| t.starts_with("image/"))
|
||||
}
|
||||
|
||||
/// The icon a web page names in its `<link>` tags, the larger apple-touch-icon first: a plain
|
||||
|
||||
Reference in New Issue
Block a user