mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-02 20:30:10 +00:00
Add Transmission/NZBGet and per-client paths and much more
Extend multi-download-client support to include Transmission and NZBGet and introduce per-client custom download paths. Adds protocol mapping and new client types, Transmission/NZBGet integration services, API CRUD and validation changes, UI components/modal updates and live path previews, and manager routing by protocol. Includes DB migrations (download_path on download_history, interactive_search_access on users), schema updates, and related processor/service fixes and tests to ensure backward compatibility and proper path resolution.
This commit is contained in:
@@ -15,7 +15,7 @@ Request → search_indexers → rank_results → download_torrent
|
||||
|
||||
1. **search_indexers** - Search Prowlarr for torrents
|
||||
2. **rank_results** - Apply ranking algorithm, select best
|
||||
3. **download_torrent** - Add to qBittorrent
|
||||
3. **download_torrent** - Add to download client (qBittorrent/Transmission/SABnzbd)
|
||||
4. **monitor_download** - Poll progress (10s intervals)
|
||||
5. **process_audiobook** - Organize files to media directory
|
||||
6. **update_plex** - Trigger scan, fuzzy match
|
||||
@@ -23,7 +23,7 @@ Request → search_indexers → rank_results → download_torrent
|
||||
## Integration Points
|
||||
|
||||
**Indexers:** Prowlarr (primary), Jackett (fallback)
|
||||
**Download Clients:** qBittorrent (primary), Transmission (fallback)
|
||||
**Download Clients:** qBittorrent or Transmission (torrent), SABnzbd (usenet) — [details](./download-clients.md)
|
||||
**Media Server:** Plex (scan + match)
|
||||
|
||||
## Job Queue (Bull)
|
||||
@@ -43,7 +43,9 @@ Request → search_indexers → rank_results → download_torrent
|
||||
## Related Docs
|
||||
|
||||
- [Prowlarr](./prowlarr.md)
|
||||
- [Download Clients](./download-clients.md) - Multi-client management, protocol routing
|
||||
- [qBittorrent](./qbittorrent.md)
|
||||
- [SABnzbd](./sabnzbd.md)
|
||||
- [Ranking Algorithm](./ranking-algorithm.md)
|
||||
- [File Organization](./file-organization.md)
|
||||
- [Plex Integration](../integrations/plex.md)
|
||||
|
||||
@@ -1,45 +1,127 @@
|
||||
# Multi-Download-Client Support
|
||||
|
||||
**Status:** ✅ Implemented | Simultaneous qBittorrent + SABnzbd support
|
||||
**Status:** ✅ Implemented | qBittorrent, Transmission, SABnzbd, and NZBGet support
|
||||
|
||||
## Overview
|
||||
Users can configure both qBittorrent (torrents) and SABnzbd (Usenet) simultaneously. System selects best release across all indexer types regardless of protocol.
|
||||
Users can configure one torrent client (qBittorrent or Transmission) and one usenet client (SABnzbd or NZBGet) simultaneously. System selects best release across all indexer types regardless of protocol.
|
||||
|
||||
**Constraint:** 1 client per type (torrent/usenet) for now; architecture supports future expansion.
|
||||
**Constraint:** 1 client per protocol (torrent/usenet). Users must remove an existing torrent client before adding a different one.
|
||||
|
||||
## Key Details
|
||||
|
||||
### Supported Clients
|
||||
|
||||
| Client | Protocol | Auth | Categories |
|
||||
|--------|----------|------|------------|
|
||||
| qBittorrent | torrent | Cookie-based (login endpoint) | Categories |
|
||||
| Transmission | torrent | HTTP Basic Auth + CSRF (`X-Transmission-Session-Id`) | Labels |
|
||||
| SABnzbd | usenet | API key | Categories |
|
||||
| NZBGet | usenet | HTTP Basic Auth (JSON-RPC) | Config-based categories |
|
||||
|
||||
### Protocol Map
|
||||
**File:** `src/lib/interfaces/download-client.interface.ts`
|
||||
|
||||
```typescript
|
||||
export const CLIENT_PROTOCOL_MAP: Record<DownloadClientType, ProtocolType> = {
|
||||
qbittorrent: 'torrent',
|
||||
sabnzbd: 'usenet',
|
||||
nzbget: 'usenet',
|
||||
transmission: 'torrent',
|
||||
};
|
||||
```
|
||||
|
||||
Used by manager's `getClientForProtocol()` and UI's protocol-level enforcement.
|
||||
|
||||
### Configuration Structure
|
||||
**Key:** `download_clients` (JSON array, replaces legacy flat keys)
|
||||
|
||||
```typescript
|
||||
interface DownloadClientConfig {
|
||||
id: string; // UUID
|
||||
type: 'qbittorrent' | 'sabnzbd';
|
||||
type: 'qbittorrent' | 'sabnzbd' | 'nzbget' | 'transmission';
|
||||
name: string; // User-friendly name
|
||||
enabled: boolean;
|
||||
url: string;
|
||||
username?: string; // qBittorrent only
|
||||
username?: string; // qBittorrent/Transmission/NZBGet only
|
||||
password: string; // Password or API key
|
||||
disableSSLVerify: boolean;
|
||||
remotePathMappingEnabled: boolean;
|
||||
remotePath?: string;
|
||||
localPath?: string;
|
||||
category?: string; // Default: 'readmeabook'
|
||||
customPath?: string; // Relative sub-path appended to download_dir
|
||||
}
|
||||
```
|
||||
|
||||
### Transmission Service
|
||||
**File:** `src/lib/integrations/transmission.service.ts`
|
||||
|
||||
- **RPC endpoint:** `POST /transmission/rpc` (JSON-RPC)
|
||||
- **CSRF:** 409 → capture `X-Transmission-Session-Id` header → retry
|
||||
- **Auth:** HTTP Basic Auth (optional)
|
||||
- **Categories:** Uses `labels` array on `torrent-add`
|
||||
- **Download path:** `download-dir` argument on `torrent-add`
|
||||
- **Torrent files:** Base64-encoded via `metainfo` field
|
||||
- **Status codes:** 0=stopped→paused, 1=check-pending→checking, 2=checking→checking, 3=download-pending→queued, 4=downloading→downloading, 5=seed-pending→seeding, 6=seeding→seeding
|
||||
- **Error handling:** `error > 0` → failed status
|
||||
- **postProcess():** No-op (same as qBittorrent)
|
||||
|
||||
### NZBGet Service
|
||||
**File:** `src/lib/integrations/nzbget.service.ts`
|
||||
|
||||
- **RPC endpoint:** `POST /jsonrpc` (JSON-RPC with Basic Auth)
|
||||
- **Auth:** HTTP Basic Auth (username + password)
|
||||
- **Categories:** Config-based (`Category1.Name`, `Category1.DestDir`), managed via `config()` + `saveconfig()`
|
||||
- **Adding NZBs:** Downloads NZB content from Prowlarr, base64-encodes, uploads via `append()`
|
||||
- **Queue status:** `listgroups(0)` — QUEUED, PAUSED, DOWNLOADING, FETCHING, PP_* (processing states)
|
||||
- **History status:** `history(false)` — SUCCESS/*, WARNING/* → completed; FAILURE/*, DELETED/* → failed
|
||||
- **Pause/Resume/Delete:** `editqueue()` with GroupPause/GroupResume/GroupDelete/HistoryDelete commands
|
||||
- **postProcess():** `editqueue('HistoryDelete')` — archives from visible history (preserves in hidden archive)
|
||||
- **IDs:** Integer NZBIDs (stored as strings in RMAB system)
|
||||
|
||||
### Per-Client Custom Download Path
|
||||
**Field:** `customPath` (optional string, blank = use base `download_dir` as-is)
|
||||
|
||||
Allows each download client to download to a different subdirectory under `download_dir`. Useful for separating torrent and usenet downloads.
|
||||
|
||||
**Path Resolution (in `createService()`):**
|
||||
```
|
||||
finalPath = config.customPath ? path.join(downloadDir, config.customPath) : downloadDir
|
||||
```
|
||||
|
||||
**Example:**
|
||||
- `download_dir` = `/downloads`, qBittorrent `customPath` = `torrents` → `/downloads/torrents`
|
||||
- `download_dir` = `/downloads`, SABnzbd `customPath` = `usenet` → `/downloads/usenet`
|
||||
- `download_dir` = `/downloads`, `customPath` = blank → `/downloads`
|
||||
|
||||
**Validation:**
|
||||
- Leading/trailing slashes stripped on save
|
||||
- Paths containing `..` rejected (frontend + API)
|
||||
- Backward-compatible: existing configs without `customPath` default to base `download_dir`
|
||||
|
||||
**Resolved path used by:**
|
||||
- Service constructors (`defaultSavePath` / `defaultDownloadDir`)
|
||||
- Category creation (qBittorrent `ensureCategory`, SABnzbd `ensureCategory`)
|
||||
- Torrent/NZB addition (save path / download-dir)
|
||||
- Remote path mapping (applied after customPath resolution)
|
||||
- Singleton getters (`getQBittorrentService`, `getSABnzbdService`)
|
||||
- Retry fallback path construction (`retry-failed-imports.processor.ts`)
|
||||
|
||||
**UI:** Modal shows real-time path preview: `Downloads to: /downloads/torrents`
|
||||
|
||||
### Download Client Manager Service
|
||||
**File:** `src/lib/services/download-client-manager.service.ts`
|
||||
|
||||
**Methods:**
|
||||
- `getClientForProtocol(protocol: 'torrent' | 'usenet')` - Get client by protocol
|
||||
- `getClientForProtocol(protocol: 'torrent' | 'usenet')` - Get client by protocol (uses `CLIENT_PROTOCOL_MAP`)
|
||||
- `hasClientForProtocol(protocol)` - Check if protocol configured
|
||||
- `getAllClients()` - List all configs
|
||||
- `testConnection(config)` - Test specific config
|
||||
- `invalidate()` - Clear cache on config change
|
||||
- `getClientServiceForProtocol(protocol)` - Get instantiated service
|
||||
|
||||
**Factory Cases:** `qbittorrent` → `QBittorrentService`, `sabnzbd` → `SABnzbdService`, `nzbget` → `NZBGetService`, `transmission` → `TransmissionService`
|
||||
|
||||
**Singleton Pattern:** Uses caching with invalidation on config changes.
|
||||
|
||||
### Protocol Filtering
|
||||
@@ -57,7 +139,7 @@ interface DownloadClientConfig {
|
||||
**Logic:**
|
||||
1. Detect protocol from result (`ProwlarrService.isNZBResult()`)
|
||||
2. Get appropriate client via manager (`getClientForProtocol()`)
|
||||
3. Route to qBittorrent or SABnzbd service
|
||||
3. Route to correct service (qBittorrent, Transmission, or SABnzbd)
|
||||
4. Create download history record
|
||||
|
||||
### Migration
|
||||
@@ -76,7 +158,7 @@ interface DownloadClientConfig {
|
||||
**POST /api/admin/settings/download-clients/test** - Test connection
|
||||
|
||||
**Validation:**
|
||||
- Only 1 client per type allowed (enforced on add)
|
||||
- Only 1 client per protocol allowed (enforced on add via `CLIENT_PROTOCOL_MAP`)
|
||||
- Test connection required before save
|
||||
- Password masking in responses (`********`)
|
||||
|
||||
@@ -86,14 +168,18 @@ interface DownloadClientConfig {
|
||||
|
||||
| Component | Purpose |
|
||||
|-----------|---------|
|
||||
| `DownloadClientManagement.tsx` | Container with add buttons + configured cards |
|
||||
| `DownloadClientCard.tsx` | Card with name, type badge, edit/delete |
|
||||
| `DownloadClientModal.tsx` | Add/edit modal with type-specific fields |
|
||||
| `DownloadClientManagement.tsx` | Container with add cards (4-column: qBittorrent, Transmission, SABnzbd, NZBGet) + configured cards; protocol-level enforcement (grayed out when protocol taken) |
|
||||
| `DownloadClientCard.tsx` | Card with name, type badge (blue=qBittorrent, green=Transmission, purple=SABnzbd, orange=NZBGet), custom path display, edit/delete |
|
||||
| `DownloadClientModal.tsx` | Add/edit modal with type-specific fields; Username shown for qBittorrent + Transmission + NZBGet; URL placeholder per-type |
|
||||
|
||||
**UI Flow:**
|
||||
1. **Add Client Section:** Two cards (qBittorrent, SABnzbd) with "Add" button or "Already configured" badge
|
||||
2. **Configured Clients:** Grid of cards showing name, type, URL, status
|
||||
3. **Modal:** Type-specific fields, SSL toggle, path mapping, test connection
|
||||
1. **Add Client Section:** Four cards (qBittorrent, Transmission, SABnzbd, NZBGet) with "Add" button or "Protocol already configured" when protocol is taken (card grayed out with `opacity-50`)
|
||||
2. **Configured Clients:** Grid of cards showing name, type, URL, custom path (if set), status
|
||||
3. **Modal:** Type-specific fields, custom download path with live preview, SSL toggle, path mapping, test connection
|
||||
|
||||
**downloadDir Prop Flow:**
|
||||
- **Settings mode:** `DownloadClientManagement` fetches from `GET /api/admin/settings` → `settings.paths.downloadDir` on mount
|
||||
- **Wizard mode:** `setup/page.tsx` passes `state.downloadDir` → `DownloadClientStep` → `DownloadClientManagement` → `DownloadClientModal`
|
||||
|
||||
## Integration Points
|
||||
|
||||
@@ -107,6 +193,8 @@ Replaced legacy form with `<DownloadClientManagement mode="settings" />`
|
||||
|
||||
Replaced single-client form with `<DownloadClientManagement mode="wizard" />`
|
||||
|
||||
**Props:** Accepts `downloadDir` from setup page state, passes to management component
|
||||
|
||||
**Validation:** At least 1 enabled client required to proceed
|
||||
|
||||
### Setup Complete API
|
||||
@@ -123,25 +211,38 @@ Accepts both legacy single client and new array format:
|
||||
**Client disabled:** Results for that protocol filtered out
|
||||
**Connection failure:** Per-download error handling (existing)
|
||||
**Mixed results:** Best release selected regardless of protocol when both clients configured
|
||||
**Custom path blank:** Uses base `download_dir` (backward-compatible default)
|
||||
**Custom path with slashes:** Leading/trailing slashes stripped automatically
|
||||
**Custom path with `..`:** Rejected by frontend validation and API validation
|
||||
**Switching torrent clients:** Must delete existing torrent client before adding Transmission (or vice versa)
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. **Migration:** Existing single-client users see config as card after update
|
||||
2. **Single client:** Configure only qBittorrent → only torrent results shown
|
||||
3. **Both clients:** Configure both → mixed results, best selected across protocols
|
||||
4. **Download routing:** Torrent result → qBittorrent; NZB result → SABnzbd
|
||||
3. **Both clients:** Configure torrent + usenet → mixed results, best selected across protocols
|
||||
4. **Download routing:** Torrent result → torrent client; NZB result → usenet client (SABnzbd or NZBGet)
|
||||
5. **Wizard:** Must add at least one client to proceed
|
||||
6. **Settings:** Can add/edit/delete/test clients; changes persist
|
||||
7. **Custom path:** Set `torrents` on torrent client → save path includes subdirectory
|
||||
8. **Custom path preview:** Modal shows resolved path in real-time as user types
|
||||
9. **Custom path persistence:** Save, reopen modal → value persists
|
||||
10. **Custom path on card:** Configured cards show custom path if set
|
||||
11. **Transmission CSRF:** First RPC call gets 409, captures session ID, retry succeeds
|
||||
12. **Protocol enforcement:** Adding qBittorrent grays out Transmission card (and vice versa)
|
||||
|
||||
## Critical Files
|
||||
|
||||
| File | Changes |
|
||||
|------|---------|
|
||||
| `src/lib/services/download-client-manager.service.ts` | **NEW** - Core multi-client service |
|
||||
| `src/lib/interfaces/download-client.interface.ts` | Client types, display names, `CLIENT_PROTOCOL_MAP` |
|
||||
| `src/lib/integrations/nzbget.service.ts` | NZBGet JSON-RPC implementation |
|
||||
| `src/lib/integrations/transmission.service.ts` | Transmission RPC implementation |
|
||||
| `src/lib/services/download-client-manager.service.ts` | Core multi-client service, protocol-based routing |
|
||||
| `src/lib/integrations/prowlarr.service.ts:379` | Protocol filtering logic (both clients = all results) |
|
||||
| `src/lib/processors/download-torrent.processor.ts:44` | Download routing (detect protocol → route) |
|
||||
| `src/app/api/admin/settings/download-clients/*` | **NEW** - CRUD API routes |
|
||||
| `src/components/admin/download-clients/*` | **NEW** - UI components (card-based) |
|
||||
| `src/app/api/admin/settings/download-clients/*` | CRUD API routes, protocol-level duplicate check |
|
||||
| `src/components/admin/download-clients/*` | UI components (3-column card layout, protocol enforcement) |
|
||||
| `src/app/admin/settings/tabs/DownloadTab/DownloadTab.tsx` | Replaced with management component |
|
||||
| `src/app/setup/steps/DownloadClientStep.tsx` | Replaced with management component |
|
||||
| `src/app/api/setup/complete/route.ts` | Save as JSON array, support legacy |
|
||||
@@ -149,5 +250,5 @@ Accepts both legacy single client and new array format:
|
||||
## Related
|
||||
|
||||
- [qBittorrent Integration](./qbittorrent.md) - Torrent client details
|
||||
- [SABnzbd Integration](./sabnzbd.md) - Usenet client details
|
||||
- [SABnzbd Integration](./sabnzbd.md) - Usenet client details (SABnzbd)
|
||||
- [Prowlarr Integration](./prowlarr.md) - Indexer search
|
||||
|
||||
@@ -37,7 +37,8 @@ Result: Douglas Adams/Stephen Fry/The Hitchhiker's Guide to the Galaxy/
|
||||
## Process
|
||||
|
||||
1. Download completes in `/downloads/[torrent-name]/` or `/downloads/[filename]` (single file)
|
||||
2. Identify audiobook files (.m4b, .m4a, .mp3) - supports both directories and single files
|
||||
1b. **Path stored** in `DownloadHistory.downloadPath` (mapped local path) for retry reliability — avoids reconstructing path from `torrentName` which may differ from actual folder name
|
||||
2. Identify audiobook files (.m4b, .m4a, .mp3, .mp4, .aa, .aax, .flac, .ogg) - supports both directories and single files
|
||||
3. Read media directory and path template from database config (`media_dir`, `audiobook_path_template`)
|
||||
4. Apply template to create target path: `[media_dir]/[template result]/`
|
||||
5. **Copy** files (not move - originals stay for seeding)
|
||||
@@ -94,6 +95,7 @@ Result: Douglas Adams/Stephen Fry/The Hitchhiker's Guide to the Galaxy/
|
||||
**Supported Formats:**
|
||||
- m4b, m4a, mp4 (AAC audiobooks)
|
||||
- mp3 (ID3v2 tags)
|
||||
- flac (Vorbis comment tags)
|
||||
|
||||
**Metadata Written:**
|
||||
- `title` - Book title
|
||||
|
||||
@@ -46,7 +46,7 @@ Free, open-source BitTorrent client with comprehensive Web API.
|
||||
- `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)
|
||||
- `download_dir` - Base download save path (joined with per-client `customPath` if configured)
|
||||
|
||||
**Optional (SSL/TLS):**
|
||||
- `download_client_disable_ssl_verify` - Disable SSL certificate verification for HTTPS (boolean as string "true"/"false", default: "false")
|
||||
@@ -65,7 +65,8 @@ Validation: All required fields checked before service initialization. Path mapp
|
||||
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
|
||||
- Ensures category save path, credentials, and `customPath` resolution are always current
|
||||
- Singleton getter resolves `customPath` from client config (consistent with manager's `createService()`)
|
||||
|
||||
## Category Management
|
||||
|
||||
@@ -73,9 +74,10 @@ Service uses singleton pattern for performance. When settings change (via admin
|
||||
|
||||
**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
|
||||
- Category save path synced with resolved download path (`download_dir` + per-client `customPath`)
|
||||
- Handles config changes: if user changes `download_dir` or `customPath`, category updates automatically
|
||||
- Uses both `createCategory` and `editCategory` APIs for reliability
|
||||
- Remote path mapping applied after `customPath` resolution (outgoing: local → remote)
|
||||
|
||||
**Why Both Create and Edit:**
|
||||
1. Create: Ensures category exists (idempotent, won't fail if exists)
|
||||
@@ -83,6 +85,11 @@ Service uses singleton pattern for performance. When settings change (via admin
|
||||
|
||||
This prevents issues where category retains old save path after user changes `download_dir` setting.
|
||||
|
||||
**Per-Client Custom Path:**
|
||||
- If `customPath` is set (e.g., `torrents`), category save path becomes `/downloads/torrents`
|
||||
- Remote path mapping applies to the resolved path: `reverseTransform(/downloads/torrents)` → remote equivalent
|
||||
- See [download-clients.md](./download-clients.md#per-client-custom-download-path) for details
|
||||
|
||||
## Remote Path Mapping
|
||||
|
||||
**Use Case:** qBittorrent runs on different machine/container with different filesystem perspective.
|
||||
@@ -167,8 +174,22 @@ interface TorrentInfo {
|
||||
completionDate: number;
|
||||
}
|
||||
|
||||
type TorrentState = 'downloading' | 'uploading' | 'stalledDL' |
|
||||
'pausedDL' | 'queuedDL' | 'checkingDL' | 'error' | 'missingFiles';
|
||||
type TorrentState =
|
||||
// Core states
|
||||
| 'downloading' | 'uploading'
|
||||
| 'stalledDL' | 'stalledUP'
|
||||
| 'pausedDL' | 'pausedUP'
|
||||
| 'queuedDL' | 'queuedUP'
|
||||
| 'checkingDL' | 'checkingUP'
|
||||
| 'error' | 'missingFiles' | 'allocating'
|
||||
// Forced states (user clicked "Force Resume")
|
||||
| 'forcedDL' | 'forcedUP'
|
||||
// Metadata fetching
|
||||
| 'metaDL' | 'forcedMetaDL'
|
||||
// qBittorrent v5.0+ (renamed paused → stopped)
|
||||
| 'stoppedDL' | 'stoppedUP'
|
||||
// Other
|
||||
| 'checkingResumeData' | 'moving';
|
||||
```
|
||||
|
||||
## Fixed Issues ✅
|
||||
@@ -216,6 +237,12 @@ type TorrentState = 'downloading' | 'uploading' | 'stalledDL' |
|
||||
- Service constructor accepts `PathMappingConfig` parameter
|
||||
- Singleton loads path mapping config from database
|
||||
|
||||
**15. Missing qBittorrent torrent states** - Monitor never detected completion for force-resumed torrents (`forcedDL`/`forcedUP`), causing infinite polling at 100%. Also missing metadata states (`metaDL`/`forcedMetaDL`), qBittorrent v5.x renamed states (`stoppedDL`/`stoppedUP`), and utility states (`checkingResumeData`/`moving`). Fixed by:
|
||||
- Adding all 8 missing states to `TorrentState` type union
|
||||
- Adding mappings to both `mapState()` (legacy) and `mapStateToDownloadStatus()` (unified interface)
|
||||
- `forcedUP` → `seeding`/`completed` enables monitor to trigger import
|
||||
- `stoppedDL`/`stoppedUP` → `paused` ensures qBittorrent v5.x compatibility
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- axios (HTTP + cookie mgmt)
|
||||
|
||||
@@ -135,12 +135,13 @@ Evaluates and scores torrents to automatically select best audiobook download.
|
||||
- Proportional credit: If 2 of 3 authors match → 10 pts (2/3 × 15)
|
||||
- Full credit: If all authors match → 15 pts
|
||||
|
||||
**2. Format Quality (25 pts max)**
|
||||
- M4B with chapters: 25
|
||||
- M4B without chapters: 22
|
||||
- M4A: 16
|
||||
- MP3: 10
|
||||
- Other: 3
|
||||
**2. Format Quality (10 pts max)**
|
||||
- M4B with chapters: 10
|
||||
- M4B without chapters: 9
|
||||
- FLAC: 7 (lossless audio)
|
||||
- M4A: 6
|
||||
- MP3: 4
|
||||
- Other: 1
|
||||
|
||||
**3. Seeder Count (15 pts max)**
|
||||
- Formula: `Math.min(15, Math.log10(seeders + 1) * 6)`
|
||||
|
||||
@@ -19,7 +19,7 @@ Free, open-source Usenet/NZB download client with comprehensive Web API. Industr
|
||||
**Format:** All requests use `output=json` for JSON responses
|
||||
|
||||
**GET /api?mode=version&output=json&apikey={key}** - Get SABnzbd version
|
||||
**GET /api?mode=addurl&name={url}&cat={category}&output=json&apikey={key}** - Add NZB by URL
|
||||
**POST /api (multipart: mode=addfile, nzbfile={binary})** - Add NZB by file upload (RMAB downloads NZB from Prowlarr, uploads to SABnzbd)
|
||||
**GET /api?mode=queue&output=json&apikey={key}** - Get active downloads
|
||||
**GET /api?mode=history&limit=100&output=json&apikey={key}** - Get completed/failed downloads
|
||||
**GET /api?mode=pause&value={nzbId}&output=json&apikey={key}** - Pause download
|
||||
@@ -37,7 +37,7 @@ Free, open-source Usenet/NZB download client with comprehensive Web API. Industr
|
||||
- `download_client_type` - Must be 'sabnzbd'
|
||||
- `download_client_url` - SABnzbd Web UI URL (supports HTTP and HTTPS)
|
||||
- `download_client_password` - API key (reuses password field)
|
||||
- `download_dir` - Download save path (passed to SABnzbd category)
|
||||
- `download_dir` - Base download save path (joined with per-client `customPath` if configured)
|
||||
|
||||
**Optional (SSL/TLS):**
|
||||
- `download_client_disable_ssl_verify` - Disable SSL certificate verification (boolean as string "true"/"false", default: "false")
|
||||
@@ -58,7 +58,8 @@ Validation: All required fields checked before service initialization. Path mapp
|
||||
Service uses singleton pattern. When settings change, singleton invalidated to force reload:
|
||||
- `invalidateSABnzbdService()` called after updating settings
|
||||
- Forces service to re-read database config
|
||||
- Ensures category and credentials are always current
|
||||
- Ensures category, credentials, and `customPath` resolution are always current
|
||||
- Singleton getter resolves `customPath` from client config (consistent with manager's `createService()`)
|
||||
|
||||
## Category Management
|
||||
|
||||
@@ -67,16 +68,21 @@ Service uses singleton pattern. When settings change, singleton invalidated to f
|
||||
**Save Path Synchronization:**
|
||||
- Category created/updated on every download (matches qBittorrent behavior)
|
||||
- Fetches SABnzbd's `complete_dir` setting via API to understand download location
|
||||
- Applies remote path mapping to translate RMAB's `download_dir` to SABnzbd's perspective
|
||||
- Applies remote path mapping to translate RMAB's resolved download path to SABnzbd's perspective
|
||||
- Calculates optimal category path (relative, absolute, or root)
|
||||
- Resolved path includes per-client `customPath` if configured (e.g., `/downloads/usenet`)
|
||||
|
||||
**Smart Path Calculation:**
|
||||
1. Get SABnzbd's `complete_dir` from `misc.complete_dir` config
|
||||
2. Apply `PathMapper.reverseTransform()` to RMAB's `download_dir`
|
||||
2. Apply `PathMapper.reverseTransform()` to RMAB's resolved download path (`download_dir` + `customPath`)
|
||||
3. Compare transformed path to `complete_dir`:
|
||||
- **Match:** Use empty string (downloads go to complete_dir root)
|
||||
- **Subdirectory:** Use relative path (e.g., `audiobooks`)
|
||||
- **Different:** Use absolute path (e.g., `/mnt/media/audiobooks`)
|
||||
- **Subdirectory:** Use relative path (e.g., `usenet`)
|
||||
- **Different:** Use absolute path (e.g., `/mnt/media/usenet`)
|
||||
|
||||
**Per-Client Custom Path:**
|
||||
- If `customPath` is set (e.g., `usenet`), category path calculated from `/downloads/usenet`
|
||||
- See [download-clients.md](./download-clients.md#per-client-custom-download-path) for details
|
||||
|
||||
## Post-Processing
|
||||
|
||||
@@ -290,9 +296,20 @@ organizePath = PathMapper.transform(sabPath, config)
|
||||
| Path Mapping | ✅ Bidirectional (same as qBit) | ✅ Bidirectional |
|
||||
| Category Sync | ✅ Every download | ✅ Every download |
|
||||
|
||||
## NZB Download Proxy
|
||||
|
||||
**RMAB proxies NZB files** — SABnzbd does not need network access to Prowlarr.
|
||||
|
||||
Prowlarr returns download URLs that point back to itself (proxy URLs like `http://prowlarr:9696/3/download?apikey=...&link=...`).
|
||||
RMAB downloads the NZB file content from that URL, then uploads it to SABnzbd via `mode=addfile` (multipart POST).
|
||||
This matches qBittorrent's pattern where RMAB downloads `.torrent` files and uploads the binary content.
|
||||
|
||||
**Result:** Download clients only need network access to RMAB. No direct Prowlarr connectivity required.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- axios (HTTP client)
|
||||
- axios (HTTP client, NZB file download)
|
||||
- form-data (multipart file upload to SABnzbd)
|
||||
- Node.js https (SSL/TLS agent)
|
||||
- JSON API responses
|
||||
|
||||
|
||||
Reference in New Issue
Block a user