// Copyright (c) 2026 Gordon Bolton. MIT License. /** * Abstract base class for all download clients. * Defines the common interface that all download clients must implement. */ class DownloadClient { /** * @param {Object} instanceConfig - Configuration for this client instance * @param {string} instanceConfig.id - Unique identifier for this instance * @param {string} instanceConfig.name - Display name for this instance * @param {string} instanceConfig.url - Base URL for the client API * @param {string} [instanceConfig.apiKey] - API key for authentication (if applicable) * @param {string} [instanceConfig.username] - Username for authentication (if applicable) * @param {string} [instanceConfig.password] - Password for authentication (if applicable) */ constructor(instanceConfig) { if (this.constructor === DownloadClient) { throw new Error('DownloadClient is an abstract class and cannot be instantiated directly'); } this.id = instanceConfig.id; this.name = instanceConfig.name; this.url = instanceConfig.url; this.apiKey = instanceConfig.apiKey; this.username = instanceConfig.username; this.password = instanceConfig.password; } /** * Get the client type identifier (e.g., 'qbittorrent', 'sabnzbd', 'transmission') * @returns {string} The client type */ getClientType() { throw new Error('getClientType() must be implemented by subclass'); } /** * Get the unique instance ID * @returns {string} The instance ID */ getInstanceId() { return this.id; } /** * Test connection to the download client * @returns {Promise} True if connection is successful */ async testConnection() { throw new Error('testConnection() must be implemented by subclass'); } /** * Get active downloads from this client * @returns {Promise>} Array of normalized download objects */ async getActiveDownloads() { throw new Error('getActiveDownloads() must be implemented by subclass'); } /** * Optional: Get client status information * @returns {Promise} Client status object or null if not supported */ async getClientStatus() { return null; // Default implementation - optional method } /** * Normalize a download object to the standard schema * @param {Object} download - Raw download object from client * @returns {NormalizedDownload} Normalized download object */ normalizeDownload(download) { throw new Error('normalizeDownload() must be implemented by subclass'); } } /** * @typedef {Object} NormalizedDownload * @property {string} id - Client-specific unique ID * @property {string} title - Download title/name * @property {'usenet'|'torrent'} type - Download type * @property {string} client - Client identifier ('sabnzbd', 'qbittorrent', 'transmission', etc.) * @property {string} instanceId - Instance identifier * @property {string} instanceName - Instance display name * @property {string} status - Normalized status (Downloading, Seeding, Paused, etc.) * @property {number} progress - Progress percentage (0-100) * @property {number} size - Total size in bytes * @property {number} downloaded - Downloaded bytes * @property {number} speed - Current speed in bytes/sec * @property {number|null} eta - Estimated time remaining in seconds, null if unknown * @property {string|undefined} category - Download category (optional) * @property {string[]|undefined} tags - Download tags (optional) * @property {string|undefined} savePath - Save path (optional) * @property {string|undefined} addedOn - Added timestamp (optional) * @property {number|undefined} arrQueueId - Sonarr/Radarr queue ID (optional) * @property {'series'|'movie'|undefined} arrType - Sonarr/Radarr type (optional) * @property {any|undefined} raw - Original client response (escape hatch) */ module.exports = DownloadClient;