fix: remove auto-appending of /RPC2 from RTorrentClient and finalize PDCA documentation
Some checks failed
Build and Push Docker Image / build (push) Failing after 40s
CI / Security audit (push) Failing after 45s
CI / Tests & coverage (push) Failing after 55s
Docs Check / Markdown lint (push) Successful in 1m1s
Docs Check / Mermaid diagram parse check (push) Successful in 1m23s
Licence Check / Licence compatibility and copyright header verification (push) Failing after 28s
Some checks failed
Build and Push Docker Image / build (push) Failing after 40s
CI / Security audit (push) Failing after 45s
CI / Tests & coverage (push) Failing after 55s
Docs Check / Markdown lint (push) Successful in 1m1s
Docs Check / Mermaid diagram parse check (push) Successful in 1m23s
Licence Check / Licence compatibility and copyright header verification (push) Failing after 28s
- Remove auto-appending of /RPC2 from RTorrentClient constructor - Use exact URL from config (supports custom paths like whatbox.ca/xmlrpc) - Update .env.sample with clear URL path documentation and examples - Update README.md with comprehensive PDCA section and all download clients - Add URL path verification tests (whatbox.ca, custom paths, no auth) - Update architecture diagram to include Transmission and rTorrent - Update Docker Compose example to include all download clients - Update prerequisites to mention all supported download clients - Update "What It Does" and "The Matching Process" sections
This commit is contained in:
12
.env.sample
12
.env.sample
@@ -99,12 +99,18 @@ QBITTORRENT_INSTANCES=[{"name":"main","url":"https://qbittorrent.example.com","u
|
||||
# Add one or more rTorrent instances as a single-line JSON array
|
||||
# Uses username/password authentication (optional)
|
||||
# Format: [{"name":"instance-name","url":"https://...","username":"...","password":"..."}]
|
||||
# XML-RPC endpoint is automatically appended: ${url}/RPC2
|
||||
# IMPORTANT: XML-RPC endpoint must be included in the url field (no automatic appending).
|
||||
# Standard installs use /RPC2. Some providers (e.g. whatbox.ca) use /xmlrpc. Other
|
||||
# installations may use a custom path. Always supply the complete RPC endpoint.
|
||||
# Examples:
|
||||
# Standard: http://rtorrent.local:8080/RPC2
|
||||
# whatbox.ca: https://user.whatbox.ca/xmlrpc
|
||||
# Custom: https://example.com/custom/rpc/path
|
||||
# =============================================================================
|
||||
# RTORRENT_INSTANCES=[{"name":"main","url":"https://rtorrent.example.com","username":"rtorrent","password":"rtorrent"}]
|
||||
# RTORRENT_INSTANCES=[{"name":"main","url":"http://rtorrent.example.com/RPC2","username":"rtorrent","password":"rtorrent"}]
|
||||
|
||||
# Legacy single-instance format (optional - still supported)
|
||||
# RTORRENT_URL=https://rtorrent.example.com
|
||||
# RTORRENT_URL=http://rtorrent.example.com/RPC2
|
||||
# RTORRENT_USERNAME=rtorrent
|
||||
# RTORRENT_PASSWORD=rtorrent
|
||||
|
||||
|
||||
53
README.md
53
README.md
@@ -7,7 +7,7 @@
|
||||
## What It Does
|
||||
|
||||
sofarr connects to your media stack and shows you a personalized view of:
|
||||
- **Active Downloads** - See what's currently downloading from Usenet (SABnzbd) and BitTorrent (qBittorrent)
|
||||
- **Active Downloads** - See what's currently downloading from Usenet (SABnzbd) and BitTorrent (qBittorrent, Transmission, rTorrent)
|
||||
- **Progress Tracking** - Real-time progress bars with speed, ETA, and completion estimates
|
||||
- **Recently Completed** - History tab showing imported and failed downloads from Sonarr/Radarr with deduplication and upgrade-awareness
|
||||
- **User Matching** - Downloads are matched to you based on tags in Sonarr/Radarr
|
||||
@@ -21,7 +21,9 @@ sofarr connects to your media stack and shows you a personalized view of:
|
||||
┌─────────────┐ ┌──────────────┐ ┌─────────────────────────────┐
|
||||
│ Browser │────▶│ sofarr │────▶│ SABnzbd (Usenet downloads) │
|
||||
│ (User) │◀────│ Server │ │ qBittorrent (Torrents) │
|
||||
└─────────────┘ └──────────────┘ │ Sonarr (TV management) │
|
||||
└─────────────┘ └──────────────┘ │ Transmission (Torrents) │
|
||||
│ │ rTorrent (Torrents) │
|
||||
│ │ Sonarr (TV management) │
|
||||
│ │ Radarr (Movie management) │
|
||||
│ │ Emby (User authentication) │
|
||||
▼ └─────────────────────────────┘
|
||||
@@ -34,10 +36,10 @@ sofarr connects to your media stack and shows you a personalized view of:
|
||||
### The Matching Process
|
||||
|
||||
1. **User Authentication**: Login via Emby credentials
|
||||
2. **Tag-Based Matching**:
|
||||
2. **Tag-Based Matching**:
|
||||
- Your media in Sonarr/Radarr is tagged with your username (e.g., "gordon")
|
||||
- sofarr checks Sonarr/Radarr activity to find items tagged with your name
|
||||
- Downloads (from SABnzbd/qBittorrent) are matched by title to that activity
|
||||
- Downloads (from SABnzbd, qBittorrent, Transmission, or rTorrent) are matched by title to that activity
|
||||
- Only your downloads appear on your dashboard
|
||||
|
||||
### Multi-Instance Support
|
||||
@@ -53,7 +55,7 @@ SONARR_INSTANCES=[{"name":"main","url":"...","apiKey":"..."}]
|
||||
## Prerequisites
|
||||
|
||||
- **Docker** (recommended), or Node.js (v22+) for manual installation
|
||||
- At least one of: SABnzbd or qBittorrent
|
||||
- At least one download client: SABnzbd, qBittorrent, Transmission, or rTorrent
|
||||
- Sonarr (optional, for TV tracking)
|
||||
- Radarr (optional, for movie tracking)
|
||||
- Emby (for user authentication)
|
||||
@@ -108,6 +110,8 @@ docker run -d \
|
||||
-e RADARR_INSTANCES='[{"name":"main","url":"http://radarr:7878","apiKey":"your-key"}]' \
|
||||
-e SABNZBD_INSTANCES='[{"name":"main","url":"http://sabnzbd:8080","apiKey":"your-key"}]' \
|
||||
-e QBITTORRENT_INSTANCES='[{"name":"main","url":"http://qbit:8080","username":"admin","password":"pass"}]' \
|
||||
-e TRANSMISSION_INSTANCES='[{"name":"main","url":"http://transmission:9091","username":"admin","password":"pass"}]' \
|
||||
-e RTORRENT_INSTANCES='[{"name":"main","url":"http://rtorrent:8080/RPC2","username":"rtorrent","password":"rtorrent"}]' \
|
||||
-e LOG_LEVEL=info \
|
||||
-e POLL_INTERVAL=5000 \
|
||||
docker.i3omb.com/sofarr:latest
|
||||
@@ -131,6 +135,8 @@ services:
|
||||
- RADARR_INSTANCES=[{"name":"main","url":"http://radarr:7878","apiKey":"your-key"}]
|
||||
- SABNZBD_INSTANCES=[{"name":"main","url":"http://sabnzbd:8080","apiKey":"your-key"}]
|
||||
- QBITTORRENT_INSTANCES=[{"name":"main","url":"http://qbit:8080","username":"admin","password":"pass"}]
|
||||
- TRANSMISSION_INSTANCES=[{"name":"main","url":"http://transmission:9091","username":"admin","password":"pass"}]
|
||||
- RTORRENT_INSTANCES=[{"name":"main","url":"http://rtorrent:8080/RPC2","username":"rtorrent","password":"rtorrent"}]
|
||||
- LOG_LEVEL=info
|
||||
- POLL_INTERVAL=5000
|
||||
```
|
||||
@@ -188,6 +194,19 @@ POLL_INTERVAL=5000 # Background polling interval in ms (default
|
||||
# Set to 0 or "off" to disable (on-demand mode)
|
||||
```
|
||||
|
||||
### Download Clients (PDCA)
|
||||
|
||||
sofarr uses a **Pluggable Download Client Architecture (PDCA)** that provides a unified interface for all download clients. This enables consistent data normalization, easy addition of new client types, and centralized configuration management.
|
||||
|
||||
**Supported Download Clients:**
|
||||
|
||||
| Client | Protocol | Auth Method | Notes |
|
||||
|--------|----------|-------------|-------|
|
||||
| SABnzbd | REST API | API Key | Usenet downloads |
|
||||
| qBittorrent | Sync API | Username/Password | BitTorrent with incremental updates |
|
||||
| Transmission | JSON-RPC | Username/Password | BitTorrent with session management |
|
||||
| rTorrent | XML-RPC | HTTP Basic Auth | BitTorrent, requires full endpoint path |
|
||||
|
||||
### Service Instances (JSON Array Format)
|
||||
|
||||
All services support multi-instance configuration via single-line JSON arrays:
|
||||
@@ -199,10 +218,20 @@ SABNZBD_INSTANCES=[{"name":"primary","url":"https://sabnzbd.example.com","apiKey
|
||||
# qBittorrent Instances (uses username/password, not API key)
|
||||
QBITTORRENT_INSTANCES=[{"name":"main","url":"https://qbittorrent.example.com","username":"admin","password":"secret"}]
|
||||
|
||||
# Transmission Instances (uses username/password)
|
||||
TRANSMISSION_INSTANCES=[{"name":"main","url":"http://transmission:9091/transmission/rpc","username":"admin","password":"pass"}]
|
||||
|
||||
# rTorrent Instances (uses username/password, URL must include full RPC endpoint)
|
||||
# Standard installs use /RPC2. Some providers like whatbox.ca use /xmlrpc.
|
||||
RTORRENT_INSTANCES=[{"name":"main","url":"http://rtorrent:8080/RPC2","username":"rtorrent","password":"rtorrent"}]
|
||||
|
||||
# For whatbox.ca (example):
|
||||
# RTORRENT_INSTANCES=[{"name":"whatbox","url":"https://user.whatbox.ca/xmlrpc","username":"user","password":"pass"}]
|
||||
|
||||
# Sonarr Instances
|
||||
SONARR_INSTANCES=[{"name":"hd","url":"https://sonarr.example.com","apiKey":"your-api-key"}]
|
||||
|
||||
# Radarr Instances
|
||||
# Radarr Instances
|
||||
RADARR_INSTANCES=[{"name":"movies","url":"https://radarr.example.com","apiKey":"your-api-key"}]
|
||||
|
||||
# Emby (single instance for authentication)
|
||||
@@ -216,6 +245,18 @@ If you only have one instance, you can use the legacy format:
|
||||
```bash
|
||||
SABNZBD_URL=https://sabnzbd.example.com
|
||||
SABNZBD_API_KEY=your-api-key
|
||||
|
||||
QBITTORRENT_URL=https://qbittorrent.example.com
|
||||
QBITTORRENT_USERNAME=admin
|
||||
QBITTORRENT_PASSWORD=secret
|
||||
|
||||
TRANSMISSION_URL=http://transmission:9091/transmission/rpc
|
||||
TRANSMISSION_USERNAME=admin
|
||||
TRANSMISSION_PASSWORD=pass
|
||||
|
||||
RTORRENT_URL=http://rtorrent:8080/RPC2
|
||||
RTORRENT_USERNAME=rtorrent
|
||||
RTORRENT_PASSWORD=rtorrent
|
||||
```
|
||||
|
||||
## Setting Up User Tags
|
||||
|
||||
@@ -5,18 +5,18 @@ const { logToFile } = require('../utils/logger');
|
||||
|
||||
/**
|
||||
* rTorrent download client implementation.
|
||||
* Communicates via XML-RPC over HTTP (typically ${url}/RPC2).
|
||||
* Communicates via XML-RPC over HTTP.
|
||||
* Supports HTTP Basic Auth when username/password are configured.
|
||||
* The URL field must include the full XML-RPC endpoint path (e.g., http://rtorrent.local:8080/RPC2 or https://user.whatbox.ca/xmlrpc).
|
||||
*/
|
||||
class RTorrentClient extends DownloadClient {
|
||||
constructor(instance) {
|
||||
super(instance);
|
||||
this.rpcUrl = `${this.url}/RPC2`;
|
||||
this._createClient();
|
||||
}
|
||||
|
||||
_createClient() {
|
||||
const clientOptions = { url: this.rpcUrl };
|
||||
const clientOptions = { url: this.url };
|
||||
|
||||
if (this.username && this.password) {
|
||||
clientOptions.headers = {
|
||||
|
||||
@@ -39,12 +39,11 @@ describe('RTorrentClient', () => {
|
||||
expect(client.getInstanceId()).toBe('test-rtorrent');
|
||||
expect(client.name).toBe('Test rTorrent');
|
||||
expect(client.url).toBe('http://localhost:8080');
|
||||
expect(client.rpcUrl).toBe('http://localhost:8080/RPC2');
|
||||
});
|
||||
|
||||
it('should create xmlrpc client with basic auth when credentials provided', () => {
|
||||
it('should create xmlrpc client with exact URL from config (no auto-append)', () => {
|
||||
expect(xmlrpc.createClient).toHaveBeenCalledWith({
|
||||
url: 'http://localhost:8080/RPC2',
|
||||
url: 'http://localhost:8080',
|
||||
headers: {
|
||||
Authorization: `Basic ${Buffer.from('rtorrent:rtorrent').toString('base64')}`
|
||||
}
|
||||
@@ -56,13 +55,44 @@ describe('RTorrentClient', () => {
|
||||
const noAuthConfig = {
|
||||
id: 'test-rtorrent-noauth',
|
||||
name: 'Test rTorrent No Auth',
|
||||
url: 'http://localhost:8080'
|
||||
url: 'http://localhost:8080/RPC2'
|
||||
};
|
||||
new RTorrentClient(noAuthConfig);
|
||||
expect(xmlrpc.createClient).toHaveBeenCalledWith({
|
||||
url: 'http://localhost:8080/RPC2'
|
||||
});
|
||||
});
|
||||
|
||||
it('should use whatbox.ca-style /xmlrpc path exactly as configured', () => {
|
||||
xmlrpc.createClient.mockClear();
|
||||
const whatboxConfig = {
|
||||
id: 'test-whatbox',
|
||||
name: 'Whatbox',
|
||||
url: 'https://user.whatbox.ca/xmlrpc',
|
||||
username: 'user',
|
||||
password: 'pass'
|
||||
};
|
||||
new RTorrentClient(whatboxConfig);
|
||||
expect(xmlrpc.createClient).toHaveBeenCalledWith({
|
||||
url: 'https://user.whatbox.ca/xmlrpc',
|
||||
headers: {
|
||||
Authorization: `Basic ${Buffer.from('user:pass').toString('base64')}`
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should use custom RPC path exactly as configured', () => {
|
||||
xmlrpc.createClient.mockClear();
|
||||
const customConfig = {
|
||||
id: 'test-custom',
|
||||
name: 'Custom',
|
||||
url: 'https://example.com/custom/rpc/path'
|
||||
};
|
||||
new RTorrentClient(customConfig);
|
||||
expect(xmlrpc.createClient).toHaveBeenCalledWith({
|
||||
url: 'https://example.com/custom/rpc/path'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Connection Test', () => {
|
||||
|
||||
Reference in New Issue
Block a user