Add indexer flag bonuses and SSL verify toggle

Implements configurable indexer flag bonuses/penalties for torrent ranking, including UI for admin settings and support in ranking-algorithm. Adds an option to disable SSL certificate verification for qBittorrent connections (for self-signed certs), with UI in both setup and admin settings, and persists the setting. Updates documentation, API routes, and ranking logic to support these features. Also includes minor UI improvements and bug fixes.
This commit is contained in:
kikootwo
2026-01-06 20:10:33 -05:00
parent ca7cac0c88
commit 23881eb670
26 changed files with 921 additions and 141 deletions
+66
View File
@@ -43,6 +43,7 @@ export interface IndexerStats {
interface ProwlarrSearchResult {
guid: string;
indexer: string;
indexerId?: number;
title: string;
size: number;
seeders: number;
@@ -51,6 +52,10 @@ interface ProwlarrSearchResult {
downloadUrl: string;
infoHash?: string;
categories?: number[];
downloadVolumeFactor?: number;
uploadVolumeFactor?: number;
indexerFlags?: string[] | number[]; // Can be string names or numeric IDs
[key: string]: any; // Allow any additional fields from Prowlarr API
}
export class ProwlarrService {
@@ -99,6 +104,11 @@ export class ProwlarrService {
const response = await this.client.get('/search', { params });
// Debug: Log first raw result to see structure
if (response.data.length > 0) {
console.log('[Prowlarr] Sample raw result from API:', JSON.stringify(response.data[0], null, 2));
}
// Transform Prowlarr results to our format
const results = response.data
.map((result: ProwlarrSearchResult) => this.transformResult(result))
@@ -232,6 +242,7 @@ export class ProwlarrService {
const result: TorrentResult = {
indexer: item.prowlarrindexer?.['#text'] || item.prowlarrindexer || 'Unknown',
indexerId: indexerId,
title: item.title || '',
size: parseInt(item.size || '0', 10),
seeders,
@@ -296,8 +307,12 @@ export class ProwlarrService {
// Extract metadata from title
const metadata = this.extractMetadata(result.title);
// Extract flags from result
const flags = this.extractFlags(result);
return {
indexer: result.indexer,
indexerId: result.indexerId,
title: result.title,
size: result.size,
seeders: result.seeders,
@@ -309,6 +324,7 @@ export class ProwlarrService {
format: metadata.format,
bitrate: metadata.bitrate,
hasChapters: metadata.hasChapters,
flags: flags.length > 0 ? flags : undefined,
};
} catch (error) {
console.error('Failed to transform result:', result, error);
@@ -316,6 +332,56 @@ export class ProwlarrService {
}
}
/**
* Extract indexer flags from Prowlarr result
*/
private extractFlags(result: ProwlarrSearchResult): string[] {
const flags: string[] = [];
// Primary method: Check for indexerFlags array (can be strings or numbers)
if (result.indexerFlags && Array.isArray(result.indexerFlags)) {
result.indexerFlags.forEach(flag => {
if (typeof flag === 'string' && flag.trim()) {
flags.push(flag.trim());
}
// Skip numeric flags - we can't map those to user-friendly names without indexer-specific mapping
});
}
// Also check for common alternative field names Prowlarr might use
const possibleFlagFields = ['flags', 'tags', 'labels'];
for (const fieldName of possibleFlagFields) {
const fieldValue = result[fieldName];
if (fieldValue && Array.isArray(fieldValue)) {
fieldValue.forEach((flag: any) => {
if (typeof flag === 'string' && flag.trim() && !flags.includes(flag.trim())) {
flags.push(flag.trim());
}
});
}
}
// Fallback: Derive flags from volume factors only if no flags were found
if (flags.length === 0) {
if (result.downloadVolumeFactor !== undefined && result.downloadVolumeFactor === 0) {
flags.push('Freeleech');
} else if (result.downloadVolumeFactor !== undefined && result.downloadVolumeFactor < 1) {
flags.push('Partial Freeleech');
}
if (result.uploadVolumeFactor !== undefined && result.uploadVolumeFactor > 1) {
flags.push('Double Upload');
}
}
// Log detected flags for debugging
if (flags.length > 0) {
console.log(`[Prowlarr] ✓ Detected flags for "${result.title.substring(0, 50)}...": [${flags.join(', ')}]`);
}
return flags;
}
/**
* Extract audiobook metadata from torrent title
*/