Mosaic polls RSS feeds at a configurable interval and auto-adds items matching a per-feed regex filter. The poller is implemented in backend/api/rss_poller.go (constructed with api.NewRSSPoller); the auto-add path goes through the same api.Service that the UI calls, so RSS-added torrents look identical to manually-added ones.

Feeds

A feed is {id, url, name, interval_min, etag, last_polled, enabled}.

  • url — fetched over HTTPS (HTTP also accepted). Validated through validateFetchURL: must be http/https, must not point at a private/loopback address. The dialer in safeHTTPClient is a second layer that catches DNS-rebind tricks. (safeHTTPClient itself does not impose a body cap; the 50 MiB body cap mentioned elsewhere is enforced by RefreshBlocklist on the blocklist refresh path, not the RSS poller.)
  • name — display label; doesn’t have to match the feed’s <title>.
  • interval_min — minutes between polls.
  • etag — last ETag header observed. Mosaic sends it back as If-None-Match on the next poll, so unchanged feeds return 304 and don’t re-process.
  • last_polled — unix seconds; updated on every successful fetch (304 included).
  • enabled — disable to pause without deleting.

Operations

Action Endpoint
List GET /api/feeds
Create POST /api/feeds
Update PUT /api/feeds
Delete DELETE /api/feeds/{id}

URL validation runs on both create and update; bad URLs return 400 with the validation error inline.

Filters

A filter belongs to a feed and is {id, feed_id, regex, category_id, save_path, enabled}.

  • regex — Go’s regexp syntax (RE2). Empty string matches every item.
  • category_id — optional. When set, the auto-added torrent has its category set after add via SetTorrentCategory. The poller does not consult the category’s default_save_path to pick a save path.
  • save_path — optional explicit save path. The poller passes fil.SavePath directly to AddMagnet. There is no automatic fallback to the category’s default_save_path — set the save path on the filter itself if you want a category-specific destination.
  • enabled — disable to pause without deleting.

The matcher tests the item’s <title> (most feeds put the descriptive name there). When at least one enabled filter on the feed matches, the item is added; if multiple match, the first one wins for category/save-path resolution.

Operations

Action Endpoint
List for a feed GET /api/feeds/{feedID}/filters
Create POST /api/filters
Update PUT /api/filters
Delete DELETE /api/filters/{id}

Examples

// Auto-add every weekly Linux ISO release matching a name pattern,
// route into the "ISOs" category.
{
  "feed_id": 1,
  "regex": "(?i)(ubuntu|fedora|debian).*?(amd64|x86_64)\\.iso",
  "category_id": 3,
  "save_path": "",
  "enabled": true
}

// Catch-all: anything from a personal feed goes to the global default.
{
  "feed_id": 4,
  "regex": "",
  "category_id": null,
  "save_path": "",
  "enabled": true
}

What gets added

The poller extracts a magnet URI from each matching item via extractMagnet (looks at <enclosure>, <link>, and text-mines <description>). Only magnet URIs are accepted — .torrent URLs are not fetched by the poller; if a feed publishes only .torrent enclosures with no matching magnet, the items are skipped. Validated magnets are added the same way POST /api/torrents/magnet would. Failures are logged but don’t abort the poll — a single malformed item won’t strand the rest of the feed.

Each successfully added torrent emits the next torrents:tick frame with the new row, so any connected SPA / API client sees it within ~1 second of the poll completing.

Caveats

  • Per-feed dedup via a seenByID set. The poller tracks seen item IDs per feed (capped at rssSeenCap = 1000 in rss_poller.go); the second time a feed item with the same ID appears, it’s skipped before reaching the engine. Combined with anacrolix refusing duplicate info-hashes, this keeps re-poll noise down. Loose-pattern filters can still log noise on the first sighting of every match.
  • No cron-style scheduling. interval_min is a fixed period from last_polled. Polls don’t align to a wall-clock minute.
  • Fetch timeout. Each fetch is bounded by safeHTTPClient’s 30-second timeout. There is no body-size cap on the RSS path — feeds that publish multi-megabyte XML payloads will be fully buffered. (The 50 MiB body cap elsewhere in the codebase is on RefreshBlocklist, not RSS.)