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.

Maintainers

Package info

github.com/fatkulnurk/torrent

pkg:composer/fatkulnurk/torrent

Transparency log

Statistics

Installs: 3

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v1.2.0 2026-07-19 14:50 UTC

This package is auto-updated.

Last update: 2026-07-19 15:00:13 UTC


README

Latest release License: MIT

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

  • Seven clients, one interface — switch drivers without rewriting app code
  • Normalized Torrent DTO — 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_endpoint defaults to RPC2. 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.01.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
qbittorrent QbittorrentProvider REST (cookie session) username + password 8080
transmission TransmissionProvider JSON-RPC username + password (optional) 9091
rtorrent RTorrentProvider XML-RPC none 8000 (RPC proxy)
deluge DelugeProvider JSON-RPC (Web UI) password 8112
rqbit RqbitProvider REST none by default 3030
aria2 Aria2Provider JSON-RPC secret token (optional) 6800
porla PorlaProvider JSON-RPC (/api/v1/jsonrpc) JWT bearer (optional if auth disabled) 1337

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
qBittorrent lscr.io/linuxserver/qbittorrent 5.2.0 http://localhost:8080 admin + session temp password
Transmission lscr.io/linuxserver/transmission 4.1.1 http://localhost:9091 admin / admin
rTorrent docker/rtorrent (crazymax-based) 0.16.7 http://localhost:8000 (RPC), :8081 (UI) none
Deluge lscr.io/linuxserver/deluge 2.1.1 http://localhost:8112 password deluge
rqbit ikatson/rqbit 9.0.0-beta.1 http://localhost:3030 none
aria2 docker/aria2 1.37.0 http://localhost:6800 secret secret123
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

qBittorrent 5.x temporary passwords change on container restart. Use make test-integration or make 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://...');

License

MIT — see LICENSE.