Skip to content

feat: add per-feed article metadata thumbnail enrichment#1130

Merged
spacecowboy merged 2 commits into
spacecowboy:masterfrom
tokenflood:feat/og-thumbnail-enrichment
May 9, 2026
Merged

feat: add per-feed article metadata thumbnail enrichment#1130
spacecowboy merged 2 commits into
spacecowboy:masterfrom
tokenflood:feat/og-thumbnail-enrichment

Conversation

@tokenflood

@tokenflood tokenflood commented May 9, 2026

Copy link
Copy Markdown
Contributor

Some feeds have missing thumbnails. This PR adds a per-feed toggle to fetch og:image/twitter:image from the article page during RSS sync and use it when an item has no better thumbnail. It also avoids duplicate header images in the article reader.

OG thumbnail enrichment

New per-feed toggle in feed settings (default OFF): Enhance thumbnails from article metadata. When enabled, each synced item that only has a body-extracted image (or no image at all) gets an extra request to the article URL to look for og:image/twitter:image meta tags. Only the <head> section of the page is streamed and the connection is closed as soon as the </head> boundary is hit, so no full page body is downloaded.

Feed-provided thumbnails (media/enclosure) are never replaced, and enrichment only applies to items whose displayed thumbnail is missing or body-derived. If a feed provides a thumbnail that is too small to use and the item falls back to a body image, enrichment is still attempted. Once an item has been upgraded to a non-body thumbnail, it won't be fetched again on later syncs. Relative image URLs are resolved against the final redirected page URL and blank metadata values are ignored.

The toggle survives encrypted feed sync and OPML import/export. There is no separate background job; enrichment happens inline during sync.

Full-text duplicate image fix

The article reader previously always showed the header thumbnail for non-body images, which could duplicate the same image inline. It now compares the header image URL against the rendered article content and only hides it when that URL already appears inline, so articles that do not duplicate the hero image in their body still keep their header image.

DB version bumped to 39 with a new feeds.fetch_og_images column (default 0). Tests cover thumbnail policy, feed item update merging, reader image dedupe, OG image extraction, head-content streaming, and serialization/export coverage for the new field.

Fixes #1125

tokenflood added 2 commits May 9, 2026 05:04
Add a per-feed setting to enhance missing/body-derived thumbnails from
article page metadata during RSS sync.

When enabled, sync fetches the article page head, extracts og:image or
twitter:image, and applies it only when the feed did not provide a usable
non-body thumbnail. Existing feed-provided thumbnails and previously
upgraded non-body thumbnails are kept.

Persist the setting in the database, encrypted feed sync, and OPML
import/export.

Add tests for thumbnail selection policy, feed-item updates, metadata
image extraction, head streaming, OPML/export serialization, and encrypted
feed serialization.
Hide the reader header thumbnail only when the same non-body image URL is
already rendered in the article content.

This keeps feed/metadata header images visible for articles that do not
duplicate them inline, while still avoiding duplicate hero images when the
article body already contains the same image.

@spacecowboy spacecowboy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it looks good. Thanks @tokenflood

One question: if the head of the article is fetched for the og:image and rest of body is not fetched, this does not mess up the okhttp cache later if the full article is fetched? I assume okhttp is smart enough to handle it and you haven't noticed anything weird in your tests I assume?

showHeaderImage: Boolean,
image: ThumbnailImage?,
contentImageUrls: Set<String>,
): Boolean = showHeaderImage && image?.fromBody == false && image.url !in contentImageUrls

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

* Reads just enough bytes to find `</head>`, keeping network usage minimal
* (typically 1–2 KB vs 100+ KB for a full page).
*/
internal fun streamHeadContent(inputStream: InputStream): String {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@tokenflood

Copy link
Copy Markdown
Contributor Author

I think it looks good. Thanks @tokenflood

One question: if the head of the article is fetched for the og:image and rest of body is not fetched, this does not mess up the okhttp cache later if the full article is fetched? I assume okhttp is smart enough to handle it and you haven't noticed anything weird in your tests I assume?

Good question. This does a normal GET, but the body is closed as soon as </head> is found. My understanding is that OkHttp only commits responses to the cache after the body has been fully read, so closing early should not result in a truncated page being stored in the cache. A later full-article fetch should therefore fetch/cache the full page normally.

I’ve also been using this locally for over a month and haven’t noticed any weird cache/full-article behavior.

@spacecowboy spacecowboy merged commit eac6e09 into spacecowboy:master May 9, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for og:image in articles

2 participants