// Copyright (c) 2026 Gordon Bolton. MIT License. const axios = require('axios'); const ArrRetriever = require('./ArrRetriever'); const { logToFile } = require('../utils/logger'); /** * Polling-based Sonarr data retriever. * Implements the ArrRetriever interface using direct HTTP polling. */ class PollingSonarrRetriever extends ArrRetriever { constructor(instanceConfig) { super(instanceConfig); } getRetrieverType() { return 'sonarr'; } /** * Get tags from Sonarr instance * @returns {Promise} Array of tag objects */ async getTags() { try { const response = await axios.get(`${this.url}/api/v3/tag`, { headers: { 'X-Api-Key': this.apiKey } }); return response.data; } catch (error) { logToFile(`[PollingSonarrRetriever] ${this.id} tags error: ${error.message}`); return []; } } /** * Get queue from Sonarr instance * @returns {Promise} Queue object with records array */ async getQueue() { const instanceName = this.name; let page = 1; let allRecords = []; let responseData = null; do { try { const response = await axios.get(`${this.url}/api/v3/queue`, { headers: { 'X-Api-Key': this.apiKey }, params: { includeSeries: true, includeEpisode: true, page, pageSize: 1000 } }); responseData = response.data; } catch (error) { console.error(`Sonarr queue fetch failed for instance ${instanceName}:`, error.response?.data || error.message); throw error; } const records = responseData.records || (Array.isArray(responseData) ? responseData : []); allRecords = allRecords.concat(records); page++; } while ( (responseData.records || (Array.isArray(responseData) ? responseData : [])).length === 1000 && page <= 50 ); return { ...responseData, records: allRecords }; } /** * Get history from Sonarr instance * @param {Object} options - Optional parameters for history fetch * @param {number} [options.pageSize=100] - Number of records to fetch per page * @param {number} [options.maxPages=1] - Maximum pages to fetch (for pagination control) * @param {string} [options.sortKey] - Field to sort by * @param {string} [options.sortDir] - Sort direction ('ascending' or 'descending') * @param {boolean} [options.includeSeries=true] - Include series data * @param {boolean} [options.includeEpisode=true] - Include episode data * @param {string} [options.startDate] - ISO date string for filtering * @returns {Promise} History object with records array */ async getHistory(options = {}) { const { pageSize = 100, maxPages = 1, sortKey, sortDir, includeSeries = true, includeEpisode = true, startDate } = options; const instanceName = this.name; let page = 1; let allRecords = []; let responseData = null; do { const params = { page, pageSize, includeSeries, includeEpisode }; if (sortKey) params.sortKey = sortKey; if (sortDir) params.sortDir = sortDir; if (startDate) params.startDate = startDate; try { const response = await axios.get(`${this.url}/api/v3/history`, { headers: { 'X-Api-Key': this.apiKey }, params }); responseData = response.data; } catch (error) { console.error(`Sonarr history fetch failed for instance ${instanceName}:`, error.response?.data || error.message); throw error; } const records = responseData.records || (Array.isArray(responseData) ? responseData : []); allRecords = allRecords.concat(records); page++; } while ( (responseData.records || (Array.isArray(responseData) ? responseData : [])).length === pageSize && page <= maxPages ); return { ...responseData, records: allRecords }; } } module.exports = PollingSonarrRetriever;