Keep Prowlarr results from distinct indexer entries separate (#1140)

Results were deduplicated on `guid` alone. When one tracker is
configured in Prowlarr as several indexer entries differing only by a
server-side search filter, all of them return the same guid for the same
torrent, so every entry but the first was silently discarded. Freeleech
and other filter-specific releases became invisible, replaced by the
unfiltered entry's copy, and which copy survived depended on query
ordering rather than user intent.

Include the indexer id in the dedup key so the entries stay distinct.

Release.source_id is qualified the same way. It keys the release cache
and becomes the download task id, so rows sharing a guid would otherwise
collide and a grab would route through whichever entry cached last,
defeating the point of showing them separately. The handler's task
matcher still accepts a bare guid or infoUrl so tasks queued before this
change still resolve.

Ordering now follows the priority already configured in Prowlarr (1-50,
lower preferred) rather than a new setting, since users curate that
ranking there and filtered entries are typically ranked ahead of their
unfiltered counterparts. The enabled-indexer list is fetched once and
reused for the enrichment check, so this costs no extra round trip.
Releases carry extra.indexer_priority, and the sort dropdown gains an
"Indexer priority" entry, ascending, alongside the existing alphabetical
"Indexer" sort; SortOption grew a default_direction for that, defaulting
to desc so "Peers" is unchanged.

PROWLARR_COLLAPSE_DUPLICATES (default off) optionally collapses a
release back to one row, resolved by the same Prowlarr priority. Left
off, every entry that carried a release keeps its own row, which is what
makes filtered results visible again.

Identity handling is defensive about partial payloads: a result that
cannot be identified is never dropped or merged, and collapse only
merges on a strong identifier (guid/downloadUrl/magnetUrl/infoUrl)
because merging on title alone would discard genuinely different
releases that share a name.

Fixes #1137

Co-authored-by: delize <4028612+delize@users.noreply.github.com>
This commit is contained in:
Andrew Doering
2026-07-28 01:06:39 -04:00
committed by GitHub
co-authored by delize
parent 0d02c6db47
commit a4086f5e06
12 changed files with 716 additions and 30 deletions
+5 -1
View File
@@ -382,7 +382,11 @@ export const ReleaseCell = ({
className={`flex items-center ${alignClass} gap-1.5 truncate text-xs text-gray-600 dark:text-gray-300`}
>
<span className={`h-2 w-2 shrink-0 rounded-full ${dotColor}`} title={protocolLabel} />
<span className="truncate">{displayValue}</span>
{/* Titled because one tracker can appear as several indexer entries whose
names share a prefix, and truncation would make the rows look identical */}
<span className="truncate" title={displayValue}>
{displayValue}
</span>
{peers && <span className="shrink-0 text-gray-400 dark:text-gray-500">{peers}</span>}
</div>
);
+1 -1
View File
@@ -956,7 +956,7 @@ const ReleaseModalSession = ({
const fromExtra = (columnConfig.extra_sort_options || []).map((opt) => ({
label: opt.label,
sortKey: opt.sort_key,
defaultDirection: 'desc' as const, // Extra sort options are typically numeric (e.g., peers)
defaultDirection: opt.default_direction ?? ('desc' as const),
}));
return [...fromColumns, ...fromExtra];
}, [sortableColumns, columnConfig.extra_sort_options]);
+1
View File
@@ -414,6 +414,7 @@ export interface LeadingCellConfig {
export interface ExtraSortOption {
label: string; // Display label in the sort dropdown
sort_key: string; // Field to sort by on the Release object
default_direction?: 'asc' | 'desc'; // Which way "best first" runs (defaults to desc)
}
export interface SourceActionButton {