@xmer/qbittorrent-client
v1.0.0
Published
TypeScript client for qBittorrent Web API with torrent management and progress monitoring
Maintainers
Readme
@xmer/qbittorrent-client
TypeScript client for qBittorrent Web API with torrent management and progress monitoring.
Features
- ✅ Full qBittorrent Web API v2 support
- ✅ TypeScript with strict mode - Complete type safety
- ✅ Event-driven progress monitoring - Real-time download progress
- ✅ Auto-reauthentication - Handles session expiry automatically
- ✅ Category management - Organize torrents with categories
- ✅ Comprehensive error handling - Specific error types for all scenarios
- ✅ 95% test coverage - Production-ready reliability
- ✅ Input validation - Magnet link validation before processing
Installation
npm install @xmer/qbittorrent-clientPrerequisites
- Node.js >= 16.0.0
- qBittorrent with Web UI enabled
Enabling qBittorrent Web UI
- Open qBittorrent
- Go to Tools → Options → Web UI
- Check Enable the Web User Interface (Remote control)
- Set your username and password (default:
admin/adminadmin) - Note the port (default:
8080)
Quick Start
import { QBittorrentClient } from '@xmer/qbittorrent-client';
// Create client
const client = new QBittorrentClient({
apiUrl: 'http://localhost:8080',
username: 'admin',
password: 'adminadmin'
});
// Connect
await client.connect();
// Add torrent
const hash = await client.addTorrent('magnet:?xt=urn:btih:...');
// Monitor progress
client.startProgressMonitoring(hash);
client.on('progress', (progress) => {
console.log(`${progress.name}: ${progress.progress.toFixed(1)}%`);
console.log(`Speed: ${(progress.downloadSpeed / 1024 / 1024).toFixed(2)} MB/s`);
console.log(`ETA: ${progress.timeRemaining}`);
});
client.on('completed', (torrent) => {
console.log(`Download completed: ${torrent.name}`);
client.stopProgressMonitoring(hash);
});
// Cleanup
await client.disconnect();API Reference
QBittorrentClient
Constructor
new QBittorrentClient(config: QBittorrentConfig)Config Options:
interface QBittorrentConfig {
apiUrl: string; // Required: qBittorrent Web API URL
username: string; // Required: Web UI username
password: string; // Required: Web UI password
defaultCategory?: string; // Optional: Default category for torrents
pollInterval?: number; // Optional: Progress poll interval (default: 5000ms)
timeout?: number; // Optional: Request timeout (default: 10000ms)
retryAttempts?: number; // Optional: Retry attempts (default: 3)
verifySsl?: boolean; // Optional: Verify SSL certs (default: true)
}Lifecycle Methods
await client.connect(): Promise<void>
await client.disconnect(): Promise<void>
await client.healthCheck(): Promise<boolean>Torrent Operations
// Add torrent
await client.addTorrent(
magnetLink: string,
options?: AddTorrentOptions
): Promise<string> // Returns torrent hash
// Get torrent info
await client.getTorrent(hash: string): Promise<Torrent>
// List torrents
await client.listTorrents(filter?: TorrentFilter): Promise<Torrent[]>
// Control torrents
await client.pauseTorrent(hash: string): Promise<void>
await client.resumeTorrent(hash: string): Promise<void>
await client.removeTorrent(hash: string, deleteFiles?: boolean): Promise<void>Category Management
// Create category
await client.createCategory(name: string, savePath?: string): Promise<void>
// List categories
await client.getCategories(): Promise<Category[]>
// Delete category
await client.deleteCategory(name: string): Promise<void>
// Assign category to torrent
await client.setTorrentCategory(hash: string, category: string): Promise<void>Progress Monitoring
// Get current progress
await client.getTorrentProgress(hash: string): Promise<TorrentProgress>
// Start monitoring
client.startProgressMonitoring(hash: string, interval?: number): void
// Stop monitoring
client.stopProgressMonitoring(hash: string): voidEvents
// Progress updates
client.on('progress', (progress: TorrentProgress) => void)
// Completion events
client.on('completed', (torrent: Torrent) => void)
// Error events
client.on('error', (error: Error, hash: string) => void)Static Utilities
// Extract hash from magnet link
QBittorrentClient.extractTorrentHash(magnetLink: string): string | null
// Validate magnet link
QBittorrentClient.validateMagnetLink(magnetLink: string): booleanUsage Examples
Basic Download
const client = new QBittorrentClient({
apiUrl: 'http://localhost:8080',
username: 'admin',
password: 'adminadmin'
});
await client.connect();
const hash = await client.addTorrent(
'magnet:?xt=urn:btih:c9e15763f722f23e98a29decdfae341b98d53056'
);
console.log(`Torrent added with hash: ${hash}`);
await client.disconnect();Progress Monitoring
const hash = await client.addTorrent('magnet:?xt=urn:btih:...');
client.startProgressMonitoring(hash);
client.on('progress', (progress) => {
console.log(`Progress: ${progress.progress.toFixed(2)}%`);
console.log(`Download: ${(progress.downloadSpeed / 1024 / 1024).toFixed(2)} MB/s`);
console.log(`Upload: ${(progress.uploadSpeed / 1024 / 1024).toFixed(2)} MB/s`);
console.log(`ETA: ${progress.timeRemaining}`);
console.log(`Downloaded: ${(progress.bytesDownloaded / 1024 / 1024 / 1024).toFixed(2)} GB`);
console.log(`Total: ${(progress.bytesTotal / 1024 / 1024 / 1024).toFixed(2)} GB`);
});
client.on('completed', (torrent) => {
console.log(`✓ Download completed: ${torrent.name}`);
console.log(`Ratio: ${torrent.ratio.toFixed(2)}`);
client.stopProgressMonitoring(hash);
});
client.on('error', (error, hash) => {
console.error(`Error monitoring ${hash}:`, error.message);
});Category Management
// Create categories
await client.createCategory('Linux', '/downloads/linux');
await client.createCategory('Movies', '/downloads/movies');
await client.createCategory('Games', '/downloads/games');
// Add torrent to category
const hash = await client.addTorrent('magnet:?xt=urn:btih:...', {
category: 'Linux'
});
// Change category
await client.setTorrentCategory(hash, 'Movies');
// List all categories
const categories = await client.getCategories();
categories.forEach(cat => {
console.log(`${cat.name}: ${cat.savePath}`);
});
// Delete category
await client.deleteCategory('Games');Advanced Options
const hash = await client.addTorrent('magnet:?xt=urn:btih:...', {
category: 'Downloads',
savePath: '/custom/path',
paused: false, // Start immediately
skipHashCheck: false,
sequentialDownload: true, // Download in order
firstLastPiecePrio: true, // Prioritize first/last pieces
dlLimit: 5242880, // 5 MB/s download limit
upLimit: 1048576 // 1 MB/s upload limit
});Filtering Torrents
// Get all torrents
const allTorrents = await client.listTorrents();
// Filter by category
const linuxTorrents = await client.listTorrents({
category: 'Linux'
});
// Filter by state
import { TorrentState } from '@xmer/qbittorrent-client';
const downloading = await client.listTorrents({
state: TorrentState.Downloading
});
const seeding = await client.listTorrents({
state: TorrentState.Uploading
});
// Filter by specific hashes
const specificTorrents = await client.listTorrents({
hashes: [hash1, hash2, hash3]
});Error Handling
import {
QBittorrentAuthError,
QBittorrentConnectionError,
InvalidMagnetLinkError,
TorrentNotFoundError,
CategoryExistsError
} from '@xmer/qbittorrent-client';
try {
await client.connect();
} catch (error) {
if (error instanceof QBittorrentAuthError) {
console.error('Authentication failed - check credentials');
} else if (error instanceof QBittorrentConnectionError) {
console.error(`Cannot connect to ${error.apiUrl}`);
}
}
try {
await client.addTorrent('invalid-magnet-link');
} catch (error) {
if (error instanceof InvalidMagnetLinkError) {
console.error('Invalid magnet link format');
}
}
try {
await client.getTorrent('nonexistent-hash');
} catch (error) {
if (error instanceof TorrentNotFoundError) {
console.error('Torrent not found');
}
}
try {
await client.createCategory('ExistingCategory');
} catch (error) {
if (error instanceof CategoryExistsError) {
console.error('Category already exists');
}
}Type Definitions
Torrent
interface Torrent {
hash: string; // 40-character hex hash
name: string;
size: number; // Total size in bytes
progress: number; // Progress 0-1
dlspeed: number; // Download speed bytes/sec
upspeed: number; // Upload speed bytes/sec
eta: number; // ETA in seconds (-1 = unknown)
state: TorrentState;
category: string;
savePath: string;
addedOn: number; // Unix timestamp
completionOn: number; // Unix timestamp (-1 if not completed)
ratio: number; // Seeding ratio
numSeeds: number;
numLeechers: number;
}TorrentProgress
interface TorrentProgress {
hash: string;
name: string;
progress: number; // 0-100
downloadSpeed: number; // bytes/sec
uploadSpeed: number; // bytes/sec
eta: number; // seconds
state: TorrentState;
bytesDownloaded: number;
bytesTotal: number;
timeRemaining: string; // Human-readable (e.g., "5m 32s")
}TorrentState
enum TorrentState {
Downloading = 'downloading',
Uploading = 'uploading', // Seeding
PausedDL = 'pausedDL',
PausedUP = 'pausedUP',
Error = 'error',
// ... and more
}Development
Running Tests
# All tests
npm test
# Unit tests only
npm test -- tests/unit
# Integration tests only
npm test -- tests/integration
# With coverage
npm test -- --coverage
# Watch mode
npm test -- --watchBuilding
npm run buildLinting
npm run lint
npm run lint:fixContributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
MIT © xmer
Changelog
See CHANGELOG.md for version history.
Support
- 🐛 Issues: GitHub Issues
- 📖 Documentation: API Docs
- 💬 Discussions: GitHub Discussions
