Files
ReadMeABook/documentation/phase3/qbittorrent.md
T
kikootwo 682836237b Implement centralized logging with RMABLogger
Replaces scattered console statements with a unified RMABLogger across backend API routes and services. Adds LOG_LEVEL-based filtering, job-aware database persistence, and context-based logging. Updates documentation to describe the new logging system and usage patterns. Also documents qBittorrent CSRF header fix
2026-01-28 11:41:58 -05:00

8.4 KiB

qBittorrent Integration

Status: Implemented

Free, open-source BitTorrent client with comprehensive Web API.

Enterprise Torrent Addition

Challenge: /api/v2/torrents/add returns only "Ok." without torrent hash.

Solution (Professional):

Magnet Links:

  1. Extract info_hash from magnet URI (deterministic)
  2. Upload via urls parameter
  3. Return extracted hash immediately

Torrent Files:

  1. Download .torrent file to memory
  2. Parse with parse-torrent (bencode decoder)
  3. Extract info_hash (SHA-1 of info dict)
  4. Upload file content via torrents parameter (multipart/form-data)
  5. Return extracted hash immediately

Benefits: Deterministic, no race conditions, works with Docker networking, handles expired URLs

API Endpoints

Base: http://qbittorrent:8080/api/v2 Auth: Cookie-based (login required)

POST /auth/login - Get session cookie POST /torrents/add - Add torrent (supports urls and torrents params, savepath override) GET /torrents/info?hashes={hash} - Get status/progress POST /torrents/pause - Pause torrent POST /torrents/resume - Resume POST /torrents/delete - Delete torrent GET /torrents/files - Get file list POST /torrents/createCategory - Create category with save path POST /torrents/editCategory - Update category save path POST /torrents/setCategory - Set category for torrent

Config

Required (database only, no env fallbacks):

  • download_client_url - qBittorrent Web UI URL (supports HTTP and HTTPS)
  • download_client_username - qBittorrent username
  • download_client_password - qBittorrent password
  • download_dir - Download save path (passed to qBittorrent for all torrents)

Optional (SSL/TLS):

  • download_client_disable_ssl_verify - Disable SSL certificate verification for HTTPS (boolean as string "true"/"false", default: "false")
    • Use when connecting to qBittorrent with self-signed certificates
    • ⚠️ Security warning: Only use on trusted private networks
    • Enhanced error messages guide users when SSL issues detected

Optional (Remote Path Mapping):

  • download_client_remote_path_mapping_enabled - Enable path mapping (boolean as string "true"/"false")
  • download_client_remote_path - Remote path prefix from qBittorrent
  • download_client_local_path - Local path prefix for ReadMeABook

Validation: All required fields checked before service initialization. Path mapping fields validated when enabled.

Singleton Invalidation: Service uses singleton pattern for performance. When settings change (via admin settings page), singleton is invalidated to force reload:

  • invalidateQBittorrentService() called after updating paths or download client settings
  • Forces service to re-read database config on next torrent addition
  • Ensures category save path and credentials are always current

Category Management

Category: readmeabook (auto-created for all torrents)

Save Path Synchronization:

  • Category created/updated on every torrent addition
  • Category save path always synced with download_dir config
  • Handles config changes: if user changes download_dir, category updates automatically
  • Uses both createCategory and editCategory APIs for reliability

Why Both Create and Edit:

  1. Create: Ensures category exists (idempotent, won't fail if exists)
  2. Edit: Updates save path to match current config (handles user changing settings)

This prevents issues where category retains old save path after user changes download_dir setting.

Remote Path Mapping

Use Case: qBittorrent runs on different machine/container with different filesystem perspective.

Example Scenario:

  • qBittorrent reports: /remote/mnt/d/done/Audiobook.Name
  • ReadMeABook needs: /downloads/Audiobook.Name
  • Mapping: Remote /remote/mnt/d/done → Local /downloads

Configuration:

  1. Admin Settings → Download Client → Enable Remote Path Mapping
  2. Enter remote path (as reported by qBittorrent)
  3. Enter local path (accessible to ReadMeABook)
  4. Test connection validates local path exists
  5. Save settings

Implementation:

  • PathMapper utility (src/lib/utils/path-mapper.ts) handles transformation
  • Applied in monitor-download.processor.ts when download completes
  • Applied in retry-failed-imports.processor.ts for failed imports
  • Uses simple prefix replacement with path normalization
  • Graceful fallback: if path doesn't match remote prefix, returns unchanged

Path Transformation:

// Input from qBittorrent
qbPath = "/remote/mnt/d/done/Audiobook.Name"

// Config
remotePath = "/remote/mnt/d/done"
localPath = "/downloads"

// Output (used for file organization)
organizePath = "/downloads/Audiobook.Name"

Validation:

  • Local path accessibility checked during test connection
  • Prevents misconfiguration before save
  • Warning shown for existing downloads (mapping only affects new downloads)

Behavior:

  • Mapping only applies when enabled
  • If path doesn't start with remote prefix, returns original (logs warning)
  • Path normalization handles trailing slashes, backslashes, redundant separators
  • Works with both content_path and constructed save_path + name

Data Models

interface TorrentInfo {
  hash: string;
  name: string;
  size: number;
  progress: number; // 0.0-1.0
  dlspeed: number; // bytes/s
  upspeed: number;
  eta: number; // seconds
  state: TorrentState;
  category: string;
  savePath: string;
  completionDate: number;
}

type TorrentState = 'downloading' | 'uploading' | 'stalledDL' |
  'pausedDL' | 'queuedDL' | 'checkingDL' | 'error' | 'missingFiles';

Fixed Issues

1. Naive torrent identification - Fixed with deterministic hash extraction 2. Docker networking issues - Fixed by downloading .torrent ourselves 3. Duplicate detection - Check if hash exists before adding 4. Config fallbacks to env - Removed, database only 5. Unclear error messages - List missing fields explicitly 6. Race condition on torrent availability - Fixed with 3s initial delay + exponential backoff retry (500ms, 1s, 2s) 7. Error logging during duplicate check - Removed console.error in getTorrent() during expected "not found" cases (duplicate checking) 8. Prowlarr magnet link redirects - Some indexers return HTTP URLs that redirect to magnet: links. Fixed by intercepting 3xx redirects before axios follows them, extracting the Location header, and routing to magnet flow if target is a magnet: link 9. Category save path not updating - When user changes download_dir setting, category keeps old path. Fixed by:

  • Checking existing categories before create/edit (avoid unnecessary 409 errors)
  • Invalidating service singleton when settings change (forces config reload)
  • Settings API calls invalidateQBittorrentService() after updating paths or credentials 10. Remote seedbox path mismatch - qBittorrent on remote machine reports different filesystem paths. Fixed by:
  • Remote path mapping feature with toggle in admin settings and setup wizard
  • PathMapper utility for prefix replacement transformation
  • Local path validation during test connection
  • Applied in download completion and import retry processors 11. HTTPS SSL certificate errors - Users with seedboxes using self-signed certificates or Let's Encrypt couldn't connect. Fixed by:
  • Optional SSL verification disable toggle in setup wizard and admin settings
  • Custom HTTPS agent with rejectUnauthorized: false when enabled
  • Enhanced error messages identifying SSL/TLS certificate issues with actionable guidance
  • Secure by default (SSL verification enabled), with clear security warnings when disabled
  • URL format: https://qbt.domain.com:443/qbittorrent fully supported 12. CSRF protection HTTP 401 errors - qBittorrent v4.1.0+ has CSRF protection enabled by default, causing authentication failures (HTTP 401) when Referer/Origin headers missing. Browsers work because they auto-send these headers. Fixed by:
  • Adding Referer and Origin headers to all login requests
  • Headers set to qBittorrent base URL (e.g., https://seedbox.example.com:443/qbittorrent)
  • Applied to both login() and testConnectionWithCredentials() methods
  • Works with all qBittorrent versions and configurations

Tech Stack

  • axios (HTTP + cookie mgmt)
  • parse-torrent (bencode + hash extraction)
  • form-data (multipart uploads)