fatkulnurk / torrent
A unified, type-safe PHP SDK for interacting with various torrent clients (qBittorrent, Transmission, rTorrent, Deluge, rqbit, aria2, Porla) via HTTP/JSON APIs.
Requires
- php: ^8.3
- guzzlehttp/guzzle: ^7.8
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.64
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^12.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Unified PHP SDK for torrent clients — one API for qBittorrent, Transmission, rTorrent, Deluge, rqbit, aria2, and Porla.
Add magnets or .torrent files, list progress, pause/resume, remove torrents, and read server version from PHP automation, seedbox tools, or web dashboards.
composer require fatkulnurk/torrent
Requires PHP 8.3+ and Guzzle 7.
Table of Contents
- Features
- Install
- Quick Start
- Methods
- Torrent data
- Driver status
- Drivers
- Testing
- Custom driver
- AI / LLM Agents
- License
Features
- Seven clients, one interface — switch drivers without rewriting app code
- Normalized
TorrentDTO — hash, name, size, progress, path, and status mapped from each client’s API - Flexible add sources — magnet URI, HTTP(S) URL (where supported), or base64-encoded
.torrent - Strict typing — PHP 8.3+, readonly data objects, PSR-4 autoload
- Tested — PHPUnit 12 unit suite + optional Docker integration against real clients; PHPStan analysed
Install
composer require fatkulnurk/torrent
# pin a minor: composer require fatkulnurk/torrent:^1.1
Quick Start
All drivers are created the same way:
use Fatkulnurk\Torrent\TorrentClientManager; $client = TorrentClientManager::make('qbittorrent', 'http://192.168.1.10:8080', [ 'username' => 'admin', 'password' => 'secret', ]);
qBittorrent
Auth is required. This package targets the qBittorrent 5.x WebUI API (pause/resume use /torrents/stop and /torrents/start).
$client = TorrentClientManager::make('qbittorrent', 'http://192.168.1.10:8080', [ 'username' => 'admin', 'password' => 'secret', ]); $client->addTorrent('magnet:?xt=urn:btih:dd8255ecdc7ca55fb0bbf81323d87062db1f6d1c&dn=Big+Buck+Bunny'); $torrents = $client->getTorrents(); foreach ($torrents as $torrent) { echo "{$torrent->name}: " . round($torrent->percentDone * 100) . "%\n"; } $client->pauseTorrent($torrents[0]->hash); $client->removeTorrent($torrents[0]->hash, true);
Transmission
Auth is optional (depends on server config). Uses legacy Transmission RPC (torrent-add, kebab-case fields) compatible with 4.x.
// With auth $client = TorrentClientManager::make('transmission', 'http://192.168.1.20:9091', [ 'username' => 'admin', 'password' => 'secret', ]); // Without auth $client = TorrentClientManager::make('transmission', 'http://192.168.1.20:9091'); $client->addTorrent('magnet:?xt=urn:btih:dd8255ecdc7ca55fb0bbf81323d87062db1f6d1c&dn=Big+Buck+Bunny'); $torrents = $client->getTorrents();
rTorrent
No authentication. XML-RPC over HTTP (SCGI proxy).
$client = TorrentClientManager::make('rtorrent', 'http://192.168.1.30:8000', [ 'rpc_endpoint' => '/', // or 'RPC2' depending on your proxy ]); $client->addTorrent('magnet:?xt=urn:btih:dd8255ecdc7ca55fb0bbf81323d87062db1f6d1c&dn=Big+Buck+Bunny'); $torrents = $client->getTorrents();
rpc_endpointdefaults toRPC2. Use/when the SCGI gateway serves XML-RPC at the root (e.g. port 8000 on the crazymax/rtorrent-rutorrent image used in this repo’s Docker stack).
Deluge
Web UI JSON-RPC; password required.
$client = TorrentClientManager::make('deluge', 'http://192.168.1.40:8112', [ 'password' => 'deluge', ]); $client->addTorrent('magnet:?xt=urn:btih:dd8255ecdc7ca55fb0bbf81323d87062db1f6d1c&dn=Big+Buck+Bunny'); $torrents = $client->getTorrents();
rqbit
No auth by default. REST API. setDownloadPath is not supported (throws RequestException).
$client = TorrentClientManager::make('rqbit', 'http://127.0.0.1:3030'); $client->addTorrent('magnet:?xt=urn:btih:dd8255ecdc7ca55fb0bbf81323d87062db1f6d1c&dn=Big+Buck+Bunny'); $torrents = $client->getTorrents(); $client->pauseTorrent($torrents[0]->hash); $client->resumeTorrent($torrents[0]->hash); $client->removeTorrent($torrents[0]->hash, false); // forget, keep files $client->removeTorrent($torrents[0]->hash, true); // delete files
aria2
Optional RPC secret token. Magnets and HTTP(S) URLs use addUri; base64 .torrent uses addTorrent.
// With secret $client = TorrentClientManager::make('aria2', 'http://127.0.0.1:6800', [ 'secret' => 'your-secret-token', ]); // Without secret $client = TorrentClientManager::make('aria2', 'http://127.0.0.1:6800'); $client->addTorrent('magnet:?xt=urn:btih:dd8255ecdc7ca55fb0bbf81323d87062db1f6d1c&dn=Big+Buck+Bunny'); $torrents = $client->getTorrents();
Porla
Porla uses JSON-RPC at /api/v1/jsonrpc with optional JWT bearer auth. Prefer setting a default save_path (required by torrents.add unless a preset supplies one). Production should use a JWT from porla auth:token; Docker integration in this repo disables auth for local testing only.
// With JWT (recommended) $client = TorrentClientManager::make('porla', 'http://127.0.0.1:1337', [ 'token' => 'your-jwt-token', 'save_path' => '/dl', ]); // Auth disabled (dev / docker only) $client = TorrentClientManager::make('porla', 'http://127.0.0.1:1337', [ 'save_path' => '/dl', ]); $client->addTorrent('magnet:?xt=urn:btih:dd8255ecdc7ca55fb0bbf81323d87062db1f6d1c&dn=Big+Buck+Bunny'); $torrents = $client->getTorrents(); $client->pauseTorrent($torrents[0]->hash); $client->setDownloadPath($torrents[0]->hash, '/dl/other');
Methods
| Method | Description |
|---|---|
addTorrent(string $source, array $options = []) |
Add magnet, URL (where supported), or base64-encoded .torrent |
getTorrents(array $filters = []) |
List torrents as Torrent[] |
getTorrent(string $hash) |
Single torrent details |
pauseTorrent(string $hash) |
Pause / stop |
resumeTorrent(string $hash) |
Resume / start |
removeTorrent(string $hash, bool $deleteFiles = false) |
Remove torrent; $deleteFiles is best-effort (rTorrent/aria2 may not wipe data via RPC) |
setDownloadPath(string $hash, string $path) |
Change download directory (not supported on rqbit) |
getServerStatus() |
ServerStatus with version info when the client exposes it |
Torrent data
Every list/get call returns a readonly Fatkulnurk\Torrent\Data\Torrent:
| Property | Type | Meaning |
|---|---|---|
hash |
string |
Info-hash or client id (aria2 uses GID) |
name |
string |
Display name |
status |
int |
Coarse, provider-normalized state (see below) |
totalSize |
int |
Total size in bytes |
leftUntilDone |
int |
Bytes remaining |
downloadDir |
string |
Download / save path |
percentDone |
float |
Progress 0.0–1.0 |
status is not a global enum. Rough guide:
| Value | Typical meaning |
|---|---|
0 |
Paused / stopped / unknown |
1 |
Downloading / queued / initializing |
2 |
Seeding / uploading / active |
3 |
Complete (aria2) |
4 |
Error |
5 |
Removed (aria2) |
Native status codes still differ per client; use this field as a coarse UI signal.
Driver status
| Method | qBittorrent | Transmission | rTorrent | Deluge | rqbit | aria2 | Porla |
|---|---|---|---|---|---|---|---|
addTorrent |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
getTorrents |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
getTorrent |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
pauseTorrent |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
resumeTorrent |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
removeTorrent |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
setDownloadPath |
✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
getServerStatus |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Icon | Meaning |
|---|---|
| ✅ | Supported |
| ❌ | Not available on that client (throws RequestException) |
Drivers
| Driver | Class | Protocol | Auth | Typical port | Client Website |
|---|---|---|---|---|---|
qbittorrent |
QbittorrentProvider |
REST (cookie session) | username + password | 8080 | qbittorrent.org |
transmission |
TransmissionProvider |
JSON-RPC | username + password (optional) | 9091 | transmissionbt.com |
rtorrent |
RTorrentProvider |
XML-RPC | none | 8000 (RPC proxy) | github.com/rakshasa/rtorrent |
deluge |
DelugeProvider |
JSON-RPC (Web UI) | password | 8112 | deluge-torrent.org |
rqbit |
RqbitProvider |
REST | none by default | 3030 | github.com/ikatson/rqbit |
aria2 |
Aria2Provider |
JSON-RPC | secret token (optional) | 6800 | aria2.github.io |
porla |
PorlaProvider |
JSON-RPC (/api/v1/jsonrpc) |
JWT bearer (optional if auth disabled) | 1337 | porla.org |
Testing
Unit tests
No external services:
make test-unit
# or
php vendor/bin/phpunit tests/Data tests/Exceptions tests/Providers tests/TorrentClientManagerTest.php
Integration tests
Against real clients via Docker Compose:
make setup make up make test-integration
make test-integration starts containers, reads the temporary qBittorrent 5.x password from logs, and runs the suite against all seven services.
Manual run:
make setup && make up docker logs torrent-qbittorrent 2>&1 | grep -oP 'temporary password is provided for this session: \K\S+' QB_PASS=<password> INTEGRATION=true QBITTORRENT_PASSWORD=$QB_PASS php vendor/bin/phpunit tests/integration
Docker services used by this repo:
| Service | Image / build | Version | URL | Auth | Client Website |
|---|---|---|---|---|---|
| qBittorrent | lscr.io/linuxserver/qbittorrent |
5.2.0 | http://localhost:8080 | admin + session temp password |
qbittorrent.org |
| Transmission | lscr.io/linuxserver/transmission |
4.1.1 | http://localhost:9091 | admin / admin |
transmissionbt.com |
| rTorrent | docker/rtorrent (crazymax-based) |
0.16.7 | http://localhost:8000 (RPC), :8081 (UI) | none | rakshasa/rtorrent |
| Deluge | lscr.io/linuxserver/deluge |
2.1.1 | http://localhost:8112 | password deluge |
deluge-torrent.org |
| rqbit | ikatson/rqbit |
9.0.0-beta.1 | http://localhost:3030 | none | ikatson/rqbit |
| aria2 | docker/aria2 |
1.37.0 | http://localhost:6800 | secret secret123 |
aria2.github.io |
| Porla | ghcr.io/porla/porla |
0.41.0 | http://localhost:1337 | auth disabled in compose (PORLA_HTTP_AUTH_DISABLED_YES_REALLY); use JWT in production |
porla.org |
qBittorrent 5.x temporary passwords change on container restart. Use
make test-integrationormake qb-password.
Custom driver
use Fatkulnurk\Torrent\TorrentClientManager; use Fatkulnurk\Torrent\Providers\AbstractProvider; use Fatkulnurk\Torrent\Data\Torrent; use Fatkulnurk\Torrent\Data\ServerStatus; class MyProvider extends AbstractProvider { protected function initialize(): void {} public function addTorrent(string $source, array $options = []): bool { /* ... */ } public function getTorrents(array $filters = []): array { /* ... */ } public function getTorrent(string $hash): Torrent { /* ... */ } public function pauseTorrent(string $hash): bool { /* ... */ } public function resumeTorrent(string $hash): bool { /* ... */ } public function removeTorrent(string $hash, bool $deleteFiles = false): bool { /* ... */ } public function setDownloadPath(string $hash, string $path): bool { /* ... */ } public function getServerStatus(): ServerStatus { /* ... */ } } TorrentClientManager::register('custom', MyProvider::class); $client = TorrentClientManager::make('custom', 'http://...');
AI / LLM Agents
If you are using an AI coding assistant, autonomous agent, or LLM (e.g. Claude Code, Cursor, OpenCode, GitHub Copilot) to contribute to or integrate this repository, refer to the full agent operational guide in AGENTS.md.
Quick Agent Reference
- Language & Runtime: PHP 8.3+ (
declare(strict_types=1);mandatory across all files). - Core Abstraction: All drivers implement
Fatkulnurk\Torrent\Contracts\TorrentClientInterface. - Factory: Instantiate clients exclusively via
Fatkulnurk\Torrent\TorrentClientManager::make($driver, $baseUrl, $config). - Immutable DTOs: Torrent records and server info are represented by
Fatkulnurk\Torrent\Data\TorrentandFatkulnurk\Torrent\Data\ServerStatus(readonly). - Verification Commands:
- Run unit tests:
php vendor/bin/phpunit tests/Data tests/Exceptions tests/Providers tests/TorrentClientManagerTest.php(ormake test-unit). - Run static analysis:
php vendor/bin/phpstan analyse. - Check code style:
php vendor/bin/php-cs-fixer fix --dry-run --diff src/.
- Run unit tests:
License
MIT — see LICENSE.