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
|
### 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
|
- 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.
|
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.
|
- In Directory and Popular, Subscribe is a plus again, not the tick that marks a feed you have.
|
||||||
|
|||||||
19
src/feed.rs
19
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
|
// 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.
|
// to /en/ would otherwise have its icon looked for in the wrong place.
|
||||||
let base = resp.url().clone();
|
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()
|
if resp.status().is_success()
|
||||||
&& let Ok(page) = resp.bytes().await
|
&& let Ok(page) = resp.bytes().await
|
||||||
&& let Some(href) = page_icon(&page)
|
&& let Some(href) = page_icon(&page)
|
||||||
&& let Ok(url) = base.join(&href)
|
&& let Ok(url) = base.join(&href)
|
||||||
|
&& is_image(client, &url).await
|
||||||
{
|
{
|
||||||
return Some(url.into());
|
return Some(url.into());
|
||||||
}
|
}
|
||||||
let ico = base.join("/favicon.ico").ok()?;
|
let ico = base.join("/favicon.ico").ok()?;
|
||||||
let resp = client.get(ico.clone()).timeout(timeout).send().await.ok()?;
|
is_image(client, &ico).await.then(|| ico.into())
|
||||||
let is_image = resp
|
}
|
||||||
|
|
||||||
|
/// 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()
|
.headers()
|
||||||
.get(reqwest::header::CONTENT_TYPE)
|
.get(reqwest::header::CONTENT_TYPE)
|
||||||
.and_then(|v| v.to_str().ok())
|
.and_then(|v| v.to_str().ok())
|
||||||
.is_some_and(|t| t.starts_with("image/"));
|
.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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The icon a web page names in its `<link>` tags, the larger apple-touch-icon first: a plain
|
/// 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