Fix critical bug: searches now respect enabled indexers

**Problem:** Prowlarr searches were querying ALL indexers instead of only
the ones enabled in user settings, causing torrents to be selected from
disabled/untrusted indexers.

**Root Cause:** The prowlarr.search() method didn't filter by indexer IDs,
and callers weren't passing enabled indexer IDs to the search.

**Changes:**
1. Added indexerIds parameter to SearchFilters interface
2. Updated prowlarr.service.ts search() to filter by indexerIds
3. Updated search-indexers.processor.ts to fetch and pass enabled indexer IDs
4. Updated interactive-search route to fetch and pass enabled indexer IDs
5. Added validation: search fails if no indexers are configured/enabled
6. Updated documentation to reflect indexer filtering behavior

**Impact:**
- Manual search: Only searches enabled indexers
- Interactive search: Only searches enabled indexers
- RSS monitoring: Already correctly filtered (no changes needed)

**Testing:** TypeScript type checking passed with no errors
This commit is contained in:
Claude
2025-12-22 16:01:06 +00:00
committed by kikootwo
parent 477a30c2eb
commit a59bbedd00
4 changed files with 60 additions and 6 deletions
+5 -1
View File
@@ -9,7 +9,7 @@ Indexer aggregator for searching multiple torrent/usenet indexers simultaneously
**Base:** `http://prowlarr:9696/api/v1` **Base:** `http://prowlarr:9696/api/v1`
**Auth:** `X-Api-Key` header **Auth:** `X-Api-Key` header
**GET /search?query={q}&categories=3030** - Search all indexers (3030 = audiobooks) **GET /search?query={q}&categories=3030&indexerIds={ids}** - Search indexers (3030 = audiobooks, ids = comma-separated indexer IDs)
**GET /indexer** - List configured indexers **GET /indexer** - List configured indexers
**GET /indexerstats** - Indexer statistics **GET /indexerstats** - Indexer statistics
**GET /feed/{indexerId}/api?t=search&cat=3030&limit=100** - RSS feed for specific indexer **GET /feed/{indexerId}/api?t=search&cat=3030&limit=100** - RSS feed for specific indexer
@@ -49,13 +49,17 @@ interface TorrentResult {
## Manual & Interactive Search ## Manual & Interactive Search
**Indexer Filtering:** All searches (manual and interactive) only query indexers enabled in settings (`prowlarr_indexers` config). If no indexers are enabled, search will fail with error.
**Manual Search** (`POST /api/requests/{id}/manual-search`) **Manual Search** (`POST /api/requests/{id}/manual-search`)
- Triggers automatic search job for requests with status: pending, failed, awaiting_search - Triggers automatic search job for requests with status: pending, failed, awaiting_search
- Searches only enabled indexers
- Uses ranking algorithm to select best torrent - Uses ranking algorithm to select best torrent
- Updates request status to 'pending' - Updates request status to 'pending'
**Interactive Search** (`POST /api/requests/{id}/interactive-search`) **Interactive Search** (`POST /api/requests/{id}/interactive-search`)
- Returns ranked torrent results for user selection - Returns ranked torrent results for user selection
- Searches only enabled indexers
- Shows table with: rank, title, size, quality score, seeders, indexer, publish date - Shows table with: rank, title, size, quality score, seeders, indexer, publish date
- Available for same statuses as manual search - Available for same statuses as manual search
- User clicks "Download" button to select specific torrent - User clicks "Download" button to select specific torrent
@@ -50,13 +50,37 @@ export async function POST(
); );
} }
// Search Prowlarr for torrents // Get enabled indexers from configuration
const { getConfigService } = await import('@/lib/services/config.service');
const configService = getConfigService();
const indexersConfigStr = await configService.get('prowlarr_indexers');
if (!indexersConfigStr) {
return NextResponse.json(
{ error: 'ConfigError', message: 'No indexers configured. Please configure indexers in settings.' },
{ status: 400 }
);
}
const indexersConfig = JSON.parse(indexersConfigStr);
const enabledIndexerIds = indexersConfig.map((indexer: any) => indexer.id);
if (enabledIndexerIds.length === 0) {
return NextResponse.json(
{ error: 'ConfigError', message: 'No indexers enabled. Please enable at least one indexer in settings.' },
{ status: 400 }
);
}
// Search Prowlarr for torrents - ONLY enabled indexers
const prowlarr = await getProwlarrService(); const prowlarr = await getProwlarrService();
const searchQuery = `${requestRecord.audiobook.title} ${requestRecord.audiobook.author}`; const searchQuery = `${requestRecord.audiobook.title} ${requestRecord.audiobook.author}`;
console.log(`[InteractiveSearch] Searching for: ${searchQuery}`); console.log(`[InteractiveSearch] Searching ${enabledIndexerIds.length} enabled indexers for: ${searchQuery}`);
const results = await prowlarr.search(searchQuery); const results = await prowlarr.search(searchQuery, {
indexerIds: enabledIndexerIds,
});
if (results.length === 0) { if (results.length === 0) {
return NextResponse.json({ return NextResponse.json({
+8 -1
View File
@@ -11,6 +11,7 @@ export interface SearchFilters {
category?: number; category?: number;
minSeeders?: number; minSeeders?: number;
maxResults?: number; maxResults?: number;
indexerIds?: number[];
} }
export interface Indexer { export interface Indexer {
@@ -72,7 +73,8 @@ export class ProwlarrService {
} }
/** /**
* Search for audiobooks across all configured indexers * Search for audiobooks across configured indexers
* If indexerIds is provided, only searches those indexers
*/ */
async search( async search(
query: string, query: string,
@@ -86,6 +88,11 @@ export class ProwlarrService {
extended: 1, // Enable searching in tags, labels, and metadata extended: 1, // Enable searching in tags, labels, and metadata
}; };
// Filter by specific indexers if provided
if (filters?.indexerIds && filters.indexerIds.length > 0) {
params.indexerIds = filters.indexerIds.join(',');
}
const response = await this.client.get('/search', { params }); const response = await this.client.get('/search', { params });
// Transform Prowlarr results to our format // Transform Prowlarr results to our format
@@ -31,6 +31,24 @@ export async function processSearchIndexers(payload: SearchIndexersPayload): Pro
}, },
}); });
// Get enabled indexers from configuration
const { getConfigService } = await import('../services/config.service');
const configService = getConfigService();
const indexersConfigStr = await configService.get('prowlarr_indexers');
if (!indexersConfigStr) {
throw new Error('No indexers configured. Please configure indexers in settings.');
}
const indexersConfig = JSON.parse(indexersConfigStr);
const enabledIndexerIds = indexersConfig.map((indexer: any) => indexer.id);
if (enabledIndexerIds.length === 0) {
throw new Error('No indexers enabled. Please enable at least one indexer in settings.');
}
await logger?.info(`Searching ${enabledIndexerIds.length} enabled indexers`);
// Get Prowlarr service // Get Prowlarr service
const prowlarr = await getProwlarrService(); const prowlarr = await getProwlarrService();
@@ -39,11 +57,12 @@ export async function processSearchIndexers(payload: SearchIndexersPayload): Pro
await logger?.info(`Searching for: "${searchQuery}"`); await logger?.info(`Searching for: "${searchQuery}"`);
// Search indexers // Search indexers - ONLY enabled ones
const searchResults = await prowlarr.search(searchQuery, { const searchResults = await prowlarr.search(searchQuery, {
category: 3030, // Audiobooks category: 3030, // Audiobooks
minSeeders: 1, // Only torrents with at least 1 seeder minSeeders: 1, // Only torrents with at least 1 seeder
maxResults: 50, // Limit results maxResults: 50, // Limit results
indexerIds: enabledIndexerIds, // Filter by enabled indexers
}); });
await logger?.info(`Found ${searchResults.length} results`); await logger?.info(`Found ${searchResults.length} results`);