RSS and Automatic Downloads - Kheopsian/Hydranos GitHub Wiki

πŸ‡¬πŸ‡§ English Β· πŸ‡«πŸ‡· FranΓ§ais

RSS and Automatic Downloads

Applies to Hydranos 4.4.

For anyone who wants Hydranos to pick up new releases from a tracker's RSS feed on its own, the way qBittorrent's RSS downloader does. You will learn what a feed and a rule are, where to set them in the RSS tab, how a rule decides to take an article, which network path a feed is fetched through, what the [rss] section changes, which API routes exist (native and qBittorrent-compatible), and what is still missing in 4.4. Terms: Concepts and Glossary.

RSS is new in 4.4: Hydranos 4.3 had no RSS at all.

What it does

  • A feed is a URL that returns RSS 2.0 or Atom (a tracker's RSS, usually with your passkey in it). Hydranos fetches it on its own interval and keeps its items, called articles.
  • A rule (an automatic download rule) is qBittorrent's rule document: text filters, an optional episode filter, the feeds it watches, and where to add what it takes (a category, optionally a save path).
  • When a rule matches an article, the article's torrent is added through the same add pipeline as any other add: the category picks the engine and the folder, the path claim, dedup and the "on add" workflows apply (Adding Torrents and Existing Data).

Do you need it? Sonarr, Radarr and autobrr read RSS themselves and send finished choices to Hydranos through torrents/add: they need nothing on this page. It is for setups without them, and for tools or scripts that drive qBittorrent's own RSS downloader through /api/v2/rss/* β€” those routes exist in 4.4 (see API).

Where Hydranos differs from a plain qBittorrent RSS setup:

  • A feed and its .torrent files are fetched through the network path of the engine the torrent will go to (tunnel, interface or proxy), not through the host's default route. See Which way out a feed takes.
  • An article taken once (added, already present, or refused) is never taken again by any rule: it is remembered by its guid and its torrent link, so a feed that republishes the same link under a new guid does not add it twice. What was taken stays remembered when the feed is deleted.
  • A refusal by the add pipeline is final; a network failure is retried. See What happens to an article.

Feeds, folders, articles, rules and the "taken" record are stored in hydra.db (tables rss_feeds, rss_folders, rss_articles, rss_rules, rss_taken) and survive a restart.

The RSS tab

The RSS tab sits between Workflows and Trackers. It is loaded when you open it and after each action, not refreshed every second; after Refresh all it reloads once more 3 seconds later. Like other tabs, it can be hidden in Config β€Ί Personalisation.

RSS feeds section:

Element What it does
Refresh all Fetches every feed in the background (POST /api/rss/refresh).
+ Feed Opens the feed form: URL (placeholder https://tracker.example/rss?passkey=...), Name (Folder\Name groups feeds; empty = the host), Refresh every (minutes, minimum 5, prefilled with the default, 30), Save, Cancel.
Red banner RSS is switched off by [rss] enabled = false: feeds are kept, nothing is fetched or added. β€” shown when [rss] enabled = false. True for the automatic side only, see Known bugs.
Feed table Feed (path, masked URL, the feed's own title), State, Last refresh, Articles ({u} unread of {n}), Every ({m} min), and per row Articles, Refresh, Edit, Delete (deletes the feed and its articles after a confirmation).

State reads, in this order: refreshing...; blocked by the kill switch (hover for the reason); the last error; not fetched yet; else OK with via … (the network path used).

Articles opens the Articles of {name} panel: Download to category (a category picker, - none - by default), Mark all read, Close, and a table Date, Title (linked to the article's page when it has one), Size (blank when the feed does not say), State (new, read, or taken by {rule}: {outcome}), with a Download button per article (greyed out when the article has no torrent link). Download adds that article now, through the same path as a rule, into the category picked above; the result line says Added: {title} or Already in this client: {title}. The article is then recorded as taken by (manual), so no rule adds it later. The tab sends only the article and the category: no save path, never paused (the API takes both, see API).

Automatic download rules section: + New rule, then the rule table Enabled (a checkbox you can click), Name, Must contain (with not: … under it for Must not contain), Category, Feeds, Last match, and per row Edit, Delete.

The rule form:

Field Rule document key Notes
Name (the rule's name) Renaming in the form renames the rule, then saves it.
Category β€” picks the engine and the folder assignedCategory / torrentParams.category - none - = no category: see Where an added torrent goes.
Must contain (placeholder show 1080p|show 2160p) mustContain See Filters.
Must not contain (placeholder cam|hdts) mustNotContain
Episode filter β€” 1x1-10;12;20-; or S01E01-S01E10 episodeFilter
Ignore later matches for β€” days, 0 = never ignoreDays
Save path β€” optional, else the category's savePath / torrentParams.save_path
Enabled (ticked by default) enabled
Use regular expressions useRegex
Smart episode filter smartFilter
Add paused addPaused / torrentParams.stopped
Feeds (one checkbox per feed) affectedFeeds (the feed URLs) A rule only sees the feeds ticked here.

Buttons: Preview, Save, Cancel. Preview lists, feed by feed (50 titles at most per feed), the articles already fetched that the draft would match, and says {n} matching articles, {m} of them would be added (unread, not taken yet). Nothing is added. Preview ignores Enabled, and it judges the rule alone: an article another rule would take first is still counted.

The form has no field for the rule's priority or its tags; it keeps the stored priority, and loses the tags (see Known bugs).

The outcome of every rule (and of every manual Download) is written in the Workflows tab's Activity table: Workflow RSS: <rule name> (or RSS: (manual)), Action rss_add, Outcome applied, skipped (detail already in this client) or failed (with the reason). See Workflows.

Feeds

  • URL: http:// or https:// only (anything else: 400). The same URL cannot be added twice (409).
  • Name and folders: a feed lives at a path like qBittorrent's, Folder\Name with a backslash. Missing folders are created. An empty name becomes the host of the URL (never the whole URL, which would show the passkey wherever the name is displayed), with (2), (3)… if taken. A path that is already a feed or folder, or that would sit inside a feed, is refused (409). Folders are visible through the API and the shim; the tab shows each feed's full path.
  • Interval: each feed has its own, refresh_secs. A new feed gets [rss] refresh_minutes (default 30 minutes); the minimum is 5 minutes, and any smaller value is raised to it. A feed is due when its last fetch is older than its interval; a feed never fetched is due at once.
  • Fetch limits: 30-second timeout, 5 MiB per feed, the client's User-Agent. At most 4 feeds are fetched at the same time by the background worker; a feed already being fetched is not fetched twice.
  • Articles kept: the newest [rss] max_articles per feed (default 200, between 1 and 10 000), merged by guid at each fetch. Older ones are dropped.
  • Errors: a failed fetch (network, HTTP status, too large, unreadable XML, closed way out) does not raise an API error; it is stored as the feed's last error and shown in State. The articles already stored stay.
  • Changing the URL (Edit, PATCH, or rss/setFeedURL) makes the rules that named the old URL follow the new one, and fetches the feed again at the next turn.
  • Deleting a feed deletes its articles, not the record of what was taken: re-adding the feed later does not re-add those torrents.

What an article is. Its identity is <guid> (<id> in Atom), else its torrent link, else its page link, else its title. Its torrent link is the enclosure of type application/x-bittorrent, else a magnet in <link>, else any other enclosure, else a magnetURI element, else <link>. Its size is the enclosure's length, else a torznab/newznab size attribute, contentLength or <size>; 0 = unknown. Its date is pubDate (published / updated in Atom); an article without a date gets the time it was first seen. The XML reader skips what it does not understand (DTDs, processing instructions, namespaces) rather than refusing the feed.

Secrets in URLs. Logs and error messages show feed and torrent URLs masked: query replaced by ?***, credentials removed, any path segment of 16 or more letters, digits, - or _ with at least one digit replaced by ***, a magnet reduced to its xt=. The tab shows the masked URL. The native API returns the URL in clear (url) and masked (url_masked): anyone with the API key can read your passkeys there.

Filters

A rule takes an article only if its title passes every filter. Case is ignored everywhere.

Must contain, without regular expressions:

  • | separates alternatives: one is enough.
  • Inside an alternative, spaces separate words that must all be in the title, in any order.
  • * (any run of characters) and ? (one character) are wildcards.
  • Empty = every title. An empty alternative (foo|) also matches every title, as in qBittorrent.

show 1080p|show 2160p takes a title that contains both show and 1080p, or both show and 2160p.

Must not contain: the same syntax; an article is rejected if any alternative matches. Empty alternatives are ignored here (cam| rejects only cam). There is no -word exclusion syntax: use Must not contain.

Use regular expressions: each of the two fields is one case-insensitive regular expression. A regular expression that does not compile is refused when the rule is saved (400), not silently ignored.

Episode filter, two syntaxes:

  • qBittorrent's: 1x2;8-15;30-; = season 1, episode 2, episodes 8 to 15, and episode 30 onwards β€” the open range continues into the following seasons. Season 0 is refused; a reversed range (15-8) is ignored.
  • Season-episode ranges: S01E01-S01E10;S02E05;S03E01-; β€” a range may cross seasons; ; or , separate entries.

Titles are read as S01E05, s1.e5, S01 E05 or 1x05. A title with no episode number β€” a season pack, for instance β€” never matches a rule that has an episode filter. A filter that does not read is refused at save (400, with the expected forms in the message).

Smart episode filter: once the rule took an episode (key S01E05, or the date YYYY-MM-DD of a daily show), the same episode is not taken again, unless the new title contains REPACK or PROPER (always allowed; there is no setting for it). Taken episodes are kept in the rule's previouslyMatchedEpisodes.

Ignore later matches for N days: after the rule takes something, an article dated less than N days after that match is not taken. 0 = off. The date compared is the article's, so older articles found later do not wake the rule either.

Order between rules: rules are tried by priority (lower first; 0 by default), then by name. The first enabled rule that names the feed and matches the article takes it; the others never see that article.

Where an added torrent goes

The rule's Category decides, exactly as for any add (Categories and Routing):

  • a category in hoard mode β†’ the hoard engine; any other category β†’ the race engine;
  • the folder is the rule's Save path if set, else the category's save path;
  • no category, or an unknown one β†’ the race engine, and no folder: unless the rule has a Save path, every add is refused with no save path: category "<name>" is unknown and no savepath was given ("" with no category) (or, for a single-file .torrent when [daemon] create_torrent_folder = true, no save path: give one, or a category that has one) β€” and a refusal is final (the article is recorded as taken). Give every rule a category that has a save path, or a save path.

A .torrent also goes through [daemon] create_torrent_folder (single-file torrents in their own folder), the race disk admission check, the path claim and dedup, and fires the when a torrent is added workflows. A magnet goes through the magnet queue and is resolved later, like any magnet add. The rule's tags (torrentParams.tags, set through the API) are applied; Add paused adds it stopped.

Download order (file priorities, sequential, first/last piece) is not an RSS setting: it is set on the torrent after the add, see Adding Torrents and Existing Data (Download order).

What happens to an article

flowchart TD
    T["Worker turn: every 30 s, or at once when a feed is added or a rule changes"] --> EN{"[rss] enabled?"}
    EN -- no --> OFF["Nothing fetched, no rule runs"]
    EN -- yes --> DUE["Feeds due: never fetched, or interval passed; 4 at a time"]
    MAN["Refresh, Refresh all, rss/refreshItem"] --> X
    DUE --> X{"Way out of the feed's engine open?"}
    X -- no --> E1["last error: not fetched, the way out is closed"]
    X -- yes --> F["Fetch through that engine's tunnel, interface or proxy: 30 s, 5 MiB"]
    F -- "network error, HTTP error, too large, not a feed" --> E2["last error set, stored articles kept"]
    F -- ok --> M["Merge by guid, keep the newest max_articles"]
    M --> EN2{"[rss] enabled?"}
    EN2 -- no --> KEEP["Articles stored, no rule runs"]
    EN2 -- yes --> A["Each unread article nothing took yet, oldest first"]
    DUE -- "after the fetches, if a rule changed: every feed, no fetch" --> A
    A --> R{"First enabled rule naming this feed that matches?"}
    R -- none --> N["Left unread, offered again at later passes"]
    R -- yes --> K{"Torrent link"}
    K -- magnet --> MQ["Magnet queue"]
    K -- "http or https" --> G["Fetch the .torrent through the way out of the rule's engine: 30 s, 10 MiB, must parse"]
    K -- "none or other scheme" --> RF
    G -- "way out closed, network error, not a torrent" --> TR["failed: stays unread and untaken, tried again after the next good fetch of this feed"]
    G -- ok --> P["Add pipeline: category picks engine and folder, path claim, dedup, on-add workflows"]
    MQ -- queued --> OK
    MQ -- "already added" --> SK
    MQ -- refused --> RF
    P -- added --> OK["Taken (added), marked read; Activity: applied"]
    P -- "already in this client" --> SK["Taken (already), marked read; Activity: skipped"]
    P -- refused --> RF["Taken (refused: reason), marked read; Activity: failed; final"]
Loading
Outcome Taken record (taken.outcome) Article Activity Retried?
Added added marked read applied β€”
The torrent is already in this client already marked read skipped, already in this client β€”
Refused by the add pipeline (no save path, race disk full, path claimed, no torrent link, an unaddable link…) refused: <reason> marked read failed, the reason No, never by a rule. Download in the Articles panel still adds it by hand.
The engine's way out is closed, a network error, or the answer is not a torrent none stays unread failed, reason + ; tried again at the next pass Yes, after the next successful fetch of that feed

After an added or already outcome, the rule's Last match is set to now and, with the smart filter on, the episode is recorded. A successful fetch always runs the rules on that feed, even if it brought nothing new: that is how a transient failure gets its second try. Saving or toggling a rule makes the next worker turn offer every feed's unread articles to the rules again, without fetching. Only one pass runs at a time, so two fetches ending together cannot take the same article twice.

Download in the Articles panel skips the rules, the enabled check and the "taken" check: it adds the article even if a rule took or refused it before, then records it as taken by (manual).

Which way out a feed takes

A tracker feed carries your passkey, and the tracker sees the address the feed is fetched from. So a feed is fetched by the way out of the engine its torrents will go to:

  • the engine of the first enabled rule (by priority, then name) that names the feed β€” through that rule's category β€” else the engine an add without a category goes to (race);
  • that engine's WireGuard tunnel, bound interface or SOCKS5 proxy, as [daemon] egress = "engine:<id>" would resolve it;
  • then that engine's announce_proxy if it has one (the route its announces take), else, when the route has no proxy yet, TYPHON_ANNOUNCE_PROXY if set;
  • an engine this node does not run: direct when the kill switch is disarmed, refused when it is armed.

A .torrent link is fetched by the way out of the engine its rule adds to (for a manual Download: the engine of the category picked). A feed shared by rules of two engines is fetched by the first rule's way out.

If the kill switch keeps that engine off the network, nothing is fetched: the feed's state is blocked by the kill switch and its last error reads not fetched: the way out of engine <id> is closed (…). The State column's via … line and the API's engine / via / blocked fields tell you which path was used. Details: VPN and Egress, Managed WireGuard.

Configuration

[rss] is optional and is not in the shipped default.toml: without it, the defaults below apply. Add it to the configuration file and restart Hydranos.

[rss]
enabled = true          # false: the background worker fetches nothing and rules add nothing
max_articles = 200      # articles kept per feed, the newest (1 to 10000)
refresh_minutes = 30    # interval given to a NEW feed; minimum 5
Key Default Effect
enabled true false stops the background worker and the rules. Feeds, articles and rules are kept. The manual Refresh and Download still work (see below).
max_articles 200 Articles kept per feed after each fetch, newest first. Clamped to 1–10 000.
refresh_minutes 30 The interval of feeds created afterwards (UI default, POST /api/rss/feeds without refresh_secs, rss/addFeed without refreshInterval). Existing feeds keep their own refresh_secs. Below 5 β†’ 5.

Not configurable: the 30-second worker tick, 4 parallel fetches, 30-second timeout, 5 MiB per feed, 10 MiB per .torrent. qBittorrent's RSS preferences (rss_refresh_interval, rss_processing_enabled, rss_auto_downloading_enabled, rss_smart_episode_filters, …) are not part of the shim's app/preferences and are not read by app/setPreferences: [rss] is the only switch.

API

All routes need the API key (X-Api-Key header or apikey parameter) or a logged-in session, like the rest of the API (Security and Access).

Native routes

Route Method What it does
/api/rss GET Everything the tab shows: enabled, max_articles, default_refresh_secs, min_refresh_secs, feeds, folders, rules.
/api/rss/feeds GET, POST List feeds; add one with {"url","path" (or "name"),"refresh_secs"} β†’ 201. A feed: id, uid, path, name, url, url_masked, title, refresh_secs, last_refresh, next_refresh, last_error, loading, articles, unread, engine, via, blocked.
/api/rss/feeds/:id PATCH, DELETE Change path (or name), url, refresh_secs; delete the feed and its articles.
/api/rss/feeds/:id/refresh POST Fetch now; answers when done, with the feed (last_error says how it went).
/api/rss/refresh POST Fetch every feed in the background: 202 {"refreshing": n}.
/api/rss/feeds/:id/articles GET Newest first (?unread=1 for unread only): guid, title, torrent_url, link, description, author, category, date, size, read, taken ({"rule","at","info_hash","outcome"} or null).
/api/rss/feeds/:id/read POST {"guid"} marks one article read, {} all.
/api/rss/feeds/:id/download POST Add one article by hand: {"guid","category","save_path","paused"} β†’ {"info_hash","already"}. 400 refused by the add (reason given), 502 the .torrent could not be fetched.
/api/rss/folders POST {"path"} β†’ 201.
/api/rss/rules GET Every rule: qBittorrent's document plus name, in priority-then-name order.
/api/rss/rules/:name PUT, DELETE Create or replace (body = the rule document); delete.
/api/rss/rules/:name/rename POST {"name"} (or new_name). 409 if taken.
/api/rss/rules/:name/matching GET What a saved rule would take: {"count","would_add","feeds":[{"id","path","name","articles"}]}. Nothing is added.
/api/rss/preview POST The same for a draft (body = a rule document).

Errors are {"error": "…"} with 400 (bad value), 404 (unknown feed, folder or rule), 409 (conflict) or 500 (store).

The rule document

The document is qBittorrent's (rss/setRule), so a rule exported from qBittorrent can be sent as it is. Fields kept and written back: enabled (default true), priority (default 0, lower first), useRegex, mustContain, mustNotContain, episodeFilter, affectedFeeds (feed URLs), ignoreDays, lastMatch (RFC 2822 date), smartFilter, previouslyMatchedEpisodes, and the add options in both of qBittorrent's spellings: 4.x's top-level assignedCategory, savePath, addPaused, and 4.6+/5.x's torrentParams {category, save_path, tags, stopped (or paused)}. Both spellings are written back; torrentParams wins when both are given.

  • A text, boolean or list field of the wrong type (enabled, useRegex, smartFilter, mustContain, mustNotContain, episodeFilter, affectedFeeds, previouslyMatchedEpisodes, assignedCategory, savePath) is refused (400). priority, ignoreDays, lastMatch, addPaused and the torrentParams fields of the wrong type are read as their default (0 / not set), without an error.
  • A save without lastMatch or previouslyMatchedEpisodes keeps the stored values, so an editor that does not show them does not reset ignoreDays and the smart filter.
  • Any other qBittorrent field is not kept: torrentContentLayout is written back as null, torrentParams.skip_checking as false, and other torrentParams options (content layout, automatic management, limits…) are dropped.
# Add a feed
curl -s -H "X-Api-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"url":"https://tracker.example.org/rss?passkey=YOUR_PASSKEY","path":"TV\\Tracker","refresh_secs":900}' \
  http://localhost:8199/api/rss/feeds

# Create a rule with tags (the web form cannot set tags)
curl -s -X PUT -H "X-Api-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"enabled":true,"mustContain":"show 1080p","mustNotContain":"cam|hdts",
       "episodeFilter":"S02E01-","smartFilter":true,
       "affectedFeeds":["https://tracker.example.org/rss?passkey=YOUR_PASSKEY"],
       "torrentParams":{"category":"tv","tags":["rss"]}}' \
  http://localhost:8199/api/rss/rules/Show%201080p

qBittorrent shim (/api/v2/rss/*)

These 13 routes of qBittorrent's RSS API exist, with the same parameters (query string or form body):

Route Parameters Notes
rss/addFolder path
rss/addFeed url, path, refreshInterval refreshInterval in seconds; minimum 300.
rss/setFeedURL path, url Rules follow the new URL.
rss/removeItem path A folder is removed with everything under it.
rss/moveItem itemPath, destPath
rss/refreshItem itemPath A feed, or every feed under a folder; background.
rss/markAsRead itemPath, articleId Without articleId: everything under the path.
rss/items withData The folder tree; each feed uid, url, and with withData=true: title, lastBuildDate, isLoading, hasError, refreshInterval, articles (id, date, title, author, category, description, torrentURL, link, isRead).
rss/setRule ruleName, ruleDef (JSON)
rss/renameRule ruleName, newRuleName
rss/removeRule ruleName
rss/rules β€” Rules keyed by name.
rss/matchingArticles ruleName {"<feed name>": [titles]}; {} for an unknown rule.

Writes are POST; items, rules and matchingArticles answer any method. A missing parameter β†’ 400 Missing parameter: X; a malformed value (bad URL, ruleDef not JSON, bad regex) β†’ 400; any other refusal β†’ 409 with the reason in plain text. Shim details: qBittorrent Shim and Automation.

curl -s -H "X-Api-Key: YOUR_API_KEY" http://localhost:8199/api/v2/rss/setRule \
  --data-urlencode 'ruleName=Show 1080p' \
  --data-urlencode 'ruleDef={"enabled":true,"mustContain":"show 1080p","affectedFeeds":["https://tracker.example.org/rss?passkey=YOUR_PASSKEY"],"assignedCategory":"tv"}'

Known bugs and limits in 4.4

Known bug in 4.4: saving a rule from the web form looks like it changes only what the form shows β€” it erases the rule's tags (torrentParams.tags, set through the API or a qBittorrent client), because the form never sends torrentParams β€” set tags with PUT /api/rss/rules/:name or rss/setRule, and do not edit those rules in the form afterwards (the Enabled checkbox in the rule table is safe: it sends the whole document back).

Known bug in 4.4: [rss] enabled = false and its banner (nothing is fetched or added) look like a full stop β€” they stop only the background worker and the rules: Refresh / Refresh all (and POST /api/rss/feeds/:id/refresh, /api/rss/refresh, rss/refreshItem) still fetch the feed, and Download (POST /api/rss/feeds/:id/download) still adds β€” delete the feeds if nothing at all may reach the tracker.

Not implemented in 4.4: the rule form has no priority and no tags field, and the Articles panel's Download has no save path and no paused option β€” use the API (priority and torrentParams.tags in the rule document; save_path and paused on /api/rss/feeds/:id/download).

Not implemented in 4.4: RSS in the MCP server and in the qBittorrent import β€” no MCP tool reads or edits feeds or rules (MCP Server), and the import wizard brings torrents, not qBittorrent's feeds or rules (Migrating from qBittorrent) β€” re-create them: rss/setRule accepts qBittorrent's rule JSON as is.

Accepted but ignored in 4.4: qBittorrent's RSS preferences (rss_refresh_interval, rss_processing_enabled, rss_auto_downloading_enabled, rss_smart_episode_filters, …) and the rule fields torrentContentLayout, skip_checking and the other torrentParams options β€” they are not stored β€” use [rss] and the fields listed in The rule document.

Also by design in 4.4:

  • [rss] is not in the shipped default.toml; it appears only once you add it.
  • A refusal by the add pipeline (no save path, race disk full…) is final for the rules; re-add such an article with Download.
  • REPACK / PROPER always get past the smart filter; episode keys are the fixed forms above (no custom episode patterns).
  • A season pack (no episode number in the title) never matches a rule with an episode filter.
  • Some RSS tab errors (Refresh, Refresh all, Delete, Mark all read, a rule's Enabled checkbox) and the delete confirmations use the browser's own alert() / confirm() dialogs, unlike the rest of the interface; the feed and rule forms, Preview and Download show their errors inline.

Related pages

Adding Torrents and Existing Data (the add pipeline, download order) Β· Categories and Routing Β· Workflows (Activity) Β· VPN and Egress Β· Managed WireGuard Β· qBittorrent Shim and Automation Β· REST API Β· Configuration: Daemon, Auth and Global Sections Β· Known Limitations

⚠️ **GitHub.com Fallback** ⚠️