Skip to content

Add CustomFeedWeb plugin to build feeds from HTML index pages - #151

Merged
id774 merged 1 commit into
masterfrom
claude/custom-feed-web-plugin-e4vqyn
Aug 17, 2026
Merged

Add CustomFeedWeb plugin to build feeds from HTML index pages#151
id774 merged 1 commit into
masterfrom
claude/custom-feed-web-plugin-e4vqyn

Conversation

@id774

@id774 id774 commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Summary

Introduces CustomFeedWeb, a new plugin that fetches HTML index pages and builds RSS feeds from the article links they contain. This enables feed generation from websites that don't publish feeds but have structured list pages.

Key Changes

  • New plugin: plugins/custom_feed/web.rb — Fetches HTML pages and extracts article links using CSS selectors, with support for:

    • Three reading modes: generic anchor extraction, selector-based link selection, or structured article nodes
    • URL resolution (relative, scheme-relative, absolute)
    • URL filtering by host, include/exclude patterns, and deduplication
    • Optional metadata extraction (title, description, publication date)
    • Configurable retry and interval settings
    • Graceful handling of fetch failures without blocking other sites
  • Comprehensive test suite: spec/plugins/custom_feed/web_spec.rb — 529 lines covering:

    • Generic mode (extracting all anchors)
    • URL resolution and normalization
    • Filtering by host, include/exclude patterns
    • Structured article extraction with item/link/title/description/date selectors
    • Channel metadata handling
    • Error handling and validation
    • Network integration test against ruby-lang.org
  • Documentation updates — Added detailed section to doc/PLUGINS.md explaining:

    • Configuration options and their defaults
    • Three reading modes and when to use each
    • URL judgment pipeline and filtering behavior
    • Integration pattern with StorePermalink for deduplication across runs
    • Dependency on nokogiri (optional plugin gem)

Implementation Details

  • Uses Nokogiri::HTML to parse pages, reading from the stream to respect document charset
  • Maintains a @seen set within a single run to deduplicate URLs (cross-run deduplication is delegated to StorePermalink)
  • Validates all configuration before making any network requests (settings errors are caught early)
  • Preserves page order rather than sorting by date (sorting is delegated to FilterSort)
  • Handles unparseable dates gracefully by logging and continuing without substitution
  • Requires nokogiri through Automatic.require_optional rather than a hard dependency

https://claude.ai/code/session_01QFKfnkUtPRHSvNWyVrfQUn

A site that publishes no feed but keeps a list page is the case
SubscriptionLink was written for, and it makes an item of every anchor on
the page: the navigation, the footer, the categories and the articles
alike. There was no way to say which of them an article is.

CustomFeedWeb fetches the pages a Recipe names and builds one feed per
page from the article links it lists. CSS selectors say where an article
is and what belongs to it: item_selector for the article node,
link_selector for the permalink, and title, description and date
selectors read inside that node. With no selectors at all it reads every
a[href], which is where a new Recipe starts.

Candidate URLs are resolved against the page they were found on, so a
relative, a ../ and a scheme-relative link all become the URL a reader
would follow. The fragment is removed, the query string is kept, and no
canonical form is guessed. What survives that is judged in a fixed
order -- HTTP or HTTPS, not the page itself, same_host, include,
exclude, already seen -- and cut to fetch_items, in the order the page
lists them.

The plugin keeps no state. What the page lists now is what it returns,
and whether an item has been published before is StorePermalink's
record, which is what the documented Recipe puts after it.

A page that could not be fetched is retried, logged and skipped, and the
other sites still produce their feeds. Settings that cannot be carried
out -- a site that is not a mapping, a missing or unfetchable url, a
pattern that is not a regular expression, a selector combination that
names no article -- are refused before anything is fetched, because a
second attempt would fail identically.

It reads HTML with nokogiri through Automatic.require_optional and
fetches through Automatic::Http, giving the parser the stream so that the
document's own meta charset is what decides its encoding. nokogiri stays
an optional plugin dependency: the framework's runtime dependencies are
unchanged, and the spec is guarded like the other specs that need it.
SubscriptionLink and FeedParser.parse_html are untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKfnkUtPRHSvNWyVrfQUn
@id774
id774 merged commit 7417748 into master Aug 17, 2026
8 checks passed
@id774
id774 deleted the claude/custom-feed-web-plugin-e4vqyn branch August 17, 2026 09:37
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.

2 participants