TypeScript API wrapper for rTorrent XML-RPC interface
DOCS: https://rtorrent.ep.workers.dev
Normalized torrent types are shared through @ctrl/shared-torrent.
npm install @ctrl/rtorrent
Requires Node.js 24 or newer.
import { RTorrent } from '@ctrl/rtorrent';
const rtorrent = new RTorrent({
baseUrl: 'http://localhost:8080',
path: '/RPC2',
username: 'admin',
password: 'admin',
timeout: 5000,
useSsl: false,
});
// Get all torrents (normalized format)
const torrents = await rtorrent.getAllData();
// Get all torrents (raw rTorrent format)
const rawTorrents = await rtorrent.getAllTorrents();
// Get specific torrent by hash
const torrent = await rtorrent.getTorrent('abc123...');
// Add from magnet URL
await rtorrent.addMagnet('magnet:?xt=urn:btih:...', {
rtorrent: {
label: 'movies',
priority: RTorrentPriority.High,
directory: '/downloads/movies',
start: true,
},
});
// Add from torrent file
const fileContent = new Uint8Array(/* torrent file bytes */);
await rtorrent.addTorrentFromFile(fileContent, {
label: 'tv-shows',
priority: RTorrentPriority.Normal,
directory: '/downloads/tv',
});
// Add using normalized interface
const normalizedTorrent = await rtorrent.normalizedAddTorrent('magnet:?xt=urn:btih:...', {
startPaused: false,
label: 'movies',
});
// Start/stop torrents
await rtorrent.startTorrent('abc123...');
await rtorrent.stopTorrent('abc123...');
// Pause/resume (normalized), resume also undoes ruTorrent's d.pause
await rtorrent.pauseTorrent('abc123...');
await rtorrent.resumeTorrent('abc123...');
// Recheck data
await rtorrent.checkHash('abc123...');
// Move in the queue
await rtorrent.queueUp('abc123...');
await rtorrent.queueDown('abc123...');
// Set priority
await rtorrent.setTorrentPriority('abc123...', RTorrentPriority.High);
// Set label/category
await rtorrent.setTorrentLabel('abc123...', 'completed');
// Remove one or more torrents, throws if a torrent doesn't exist
await rtorrent.removeTorrent(['abc123...', 'def456...'], false); // false = don't delete files
// Get torrent files
const files = await rtorrent.getTorrentFiles('abc123...');
// Skip a file, file priorities are 0 off, 1 normal, 2 high
await rtorrent.setFilePriority('abc123...', files[0].index, RTorrentFilePriority.Off);
// Get torrent trackers
const trackers = await rtorrent.getTorrentTrackers('abc123...');
await rtorrent.setTrackerEnabled('abc123...', trackers[0].index, false);
// Get torrent peers
const peers = await rtorrent.getTorrentPeers('abc123...');
// Get system information
const systemInfo = await rtorrent.getSystemInfo();
const version = await rtorrent.getVersion();
rTorrent has no per-torrent limits. Torrents are assigned to named throttle groups and torrents without a group use the global limit.
// Global limits in bytes/s, 0 is unlimited
await rtorrent.setGlobalDownloadRateLimit(1024 * 1024);
await rtorrent.setGlobalUploadRateLimit(512 * 1024);
// Create a throttle group (KiB/s) and assign a stopped torrent to it
await rtorrent.setThrottleGroup('slow', 100, 50);
await rtorrent.stopTorrent('abc123...');
await rtorrent.setTorrentThrottle('abc123...', 'slow');
// Limit of the torrent's throttle group in bytes/s
const downloadLimit = await rtorrent.getDownloadRateLimit('abc123...');
const uploadLimit = await rtorrent.getUploadRateLimit('abc123...');
// Get available views
const views = await rtorrent.getViews();
// Add torrent to view
await rtorrent.addTorrentToView('abc123...', 'completed');
// Remove torrent from view
await rtorrent.removeTorrentFromView('abc123...', 'completed');
// Export client state for persistence
const state = rtorrent.exportState();
// Restore client from state
const restoredClient = RTorrent.createFromState(config, state);
interface RTorrentConfig {
baseUrl: string; // rTorrent XML-RPC endpoint URL
path?: string; // XML-RPC path (default: '/RPC2')
username?: string; // Username for HTTP Basic Authentication
password?: string; // Password for HTTP Basic Authentication
timeout?: number; // Request timeout in ms (default: 5000)
useSsl?: boolean; // Use HTTPS (default: false)
}
enum RTorrentPriority {
DoNotDownload = 0,
Low = 1,
Normal = 2,
High = 3,
}
enum RTorrentTorrentState {
Stopped = 0,
Started = 1,
}
A started torrent can still be paused, check isActive.
Start a test rTorrent container:
docker run -d \
--name=rutorrent \
-e PUID=1000 \
-e PGID=1000 \
-e TZ=Etc/UTC \
-p 8080:80 \
-p 49164:49164 \
-p 49164:49164/udp \
--restart unless-stopped \
lscr.io/linuxserver/rutorrent:latest
The XML-RPC endpoint will be available at http://localhost:8080/RPC2.
This library communicates with rTorrent using its XML-RPC interface. Key methods used:
d.multicall2 - Get torrent information, still available on rTorrent 0.16 and supported by older versions that don't have d.multicallload.start / load.normal - Add torrents from URLload.raw_start / load.raw - Add torrents from filed.erase - Remove torrentsd.start / d.stop - Control torrent statesystem.client_version - Get version informationFor complete API documentation, see the rTorrent XML-RPC wiki.
rTorrent 0.16.9+ lets the SCGI proxy mark connections with the UNTRUSTED_CONNECTION header, which limits them to an allowlist of safe methods. Write commands this library uses (adding, removing, labeling, throttling) can fault on those connections, so point the client at a trusted endpoint. See the 0.16.9 release notes.
Failed requests throw a TorrentClientError from @ctrl/shared-torrent with a code of torrent_not_found, unauthorized, request_failed or client_error, the http status when there is one and the original error as the cause. XML-RPC faults are client_error, except rTorrent's Could not find info-hash. which is torrent_not_found.
import { TorrentClientError } from '@ctrl/rtorrent';
try {
await client.getTorrent('torrent-hash');
} catch (error) {
if (error instanceof TorrentClientError && error.code === 'torrent_not_found') {
// not in the client
}
}
label is stored in d.custom1, the same field ruTorrent usesremoveTorrent(id, true) throws, rTorrent's API can't delete files. Radarr removes them from disk itselfqueueUp/queueDown change the torrent priority and queuePosition is always 0dateAdded is when rTorrent loaded the torrent (d.load_date), not the .torrent creation dateAll of the following npm modules provide the same normalized functions along with supporting the unique apis for each client.
Usenet clients with the same normalized approach:
MIT