Skip to content

Add torrent support: Torznab indexers + qBittorrent client #550

Description

@andreasorbelli

Summary

CLU can currently acquire issues from GetComics, Usenet (SABnzbd/NZBGet via Newznab indexers), and DC++ (AirDC++). This adds torrents as a fourth source, using the same open, standard protocols the Usenet integration already uses — no vendor-specific code:

  • A Torznab indexer adapter (the open torrent-indexer protocol; this is exactly what Prowlarr exposes per tracker, but the client only speaks the open protocol — same relationship the existing Newznab client already has with Prowlarr's Newznab endpoints).
  • A qBittorrent download-client adapter to submit and track the results.

Why

Torznab/qBittorrent is the natural counterpart to the existing Newznab/SABnzbd+NZBGet Usenet path, and to DC++/AirDC++. Users who already run Prowlarr for torrent trackers get the same "configure an indexer + a client, then CLU finds and grabs wanted issues automatically" experience already available for Usenet.

Design

The existing indexer/download-client architecture is already built for this: indexers.indexer_type and download_clients.client_group are free-text columns (no schema change needed to add new values), and set_active_download_client/get_active_download_client already scope "single active client" per client_group, so a torrent client can be active alongside a Usenet client. The plan:

  • models/indexers/torznab_indexer.py — new IndexerType.TORZNAB adapter, mirrors newznab_indexer.py (t=caps connection test, t=search result parsing, plus Torznab's seeders/peers attrs).
  • models/download_clients/qbittorrent_client.py — new ClientType.QBITTORRENT adapter (client_group="torrent"), using qBittorrent's cookie-session auth and torrents/add/torrents/info API. A submitted torrent's hash is resolved via a unique tags value passed on submission (avoids hand-rolling bencode/magnet-hash parsing).
  • models/torrent.py — new orchestration module mirroring models/usenet.py (indexer-driven search + scoring) and models/dcpp.py (own crash-recovery job ledger + poller, since qBittorrent is a separate long-running daemon like AirDC++). Reuses the existing score_getcomics_result/accept_result scorer, the shared notify_download_terminal hook, and the shared WATCH-folder import mover already used by Usenet and DC++.
  • A new torrent_jobs table (mirrors dcpp_jobs) for crash recovery.
  • Wiring into models/download_sources.py (KNOWN_SOURCES, get_external_sources()) so the existing nightly sweep, per-series "Check for Missing Issues", and manual search modal all pick up the new source automatically, plus the small set of hand-written routes (/api/torrent/search, /grab, /downloads) that mirror the existing Usenet/DC++ ones.
  • Bundles a small existing-code fix: search_usenet_for_issue currently doesn't filter indexers by indexer_type, so once Torznab indexers exist they'd otherwise get fed to the NZB search too.

Full test coverage mirrors the existing Usenet/DC++/Newznab/AirDC++ test files (mocked adapter tests, DB integration tests, route tests).

Scope

New: Torznab indexer, qBittorrent client, models/torrent.py, torrent_jobs table, /api/torrent/* routes, config UI + manual-search-modal entries for the new source, tests.
Not in scope: any integration with Prowlarr's "Applications" indexer-push feature — this only consumes the open Torznab protocol, the same way the existing code consumes Newznab.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions