Skip to content

feat(crossseed): add RSS source filters for categories and tags - #757

Merged
s0up4200 merged 4 commits into
mainfrom
feat/crossseed-rss-source-filters
Dec 12, 2025
Merged

feat(crossseed): add RSS source filters for categories and tags#757
s0up4200 merged 4 commits into
mainfrom
feat/crossseed-rss-source-filters

Conversation

@s0up4200

@s0up4200 s0up4200 commented Dec 12, 2025

Copy link
Copy Markdown
Collaborator

Summary

  • Add ability to filter which local torrents are considered when searching for cross-seed matches in RSS Automation
  • Users can include or exclude torrents based on categories and tags via MultiSelect dropdowns
  • Dropdowns are populated with actual categories/tags aggregated from all selected target instances

Test plan

  • Enable RSS Automation with target instances selected
  • Verify category/tag dropdowns populate from target instances
  • Test include filters: only torrents matching selected categories/tags should be considered
  • Test exclude filters: torrents with excluded categories/tags should be skipped
  • Verify empty filters means all torrents are considered
  • Check debug logs show filter reduction counts when filters are active

Closes #730

Summary by CodeRabbit

  • New Features
    • Added RSS source filtering to cross-seed automation: include/exclude torrents by category and tag; controls exposed in the Cross-Seed UI and saved with automation settings.
    • UI: aggregated source metadata for building filter options and a revamped Recent RSS Runs panel with collapsible per-run details and run-level statistics.
    • Run results now include indexer name for clearer cross-seed context.

✏️ Tip: You can customize this high-level summary in your review settings.

Add ability to filter which local torrents are considered when searching
for cross-seed matches in RSS Automation. Users can now include or exclude
torrents based on categories and tags.

- Add 4 new filter fields to CrossSeedAutomationSettings model
- Add database migration for new columns (JSON arrays)
- Implement matchesRSSSourceFilters() with case-sensitive matching
- Add debug logging when filters reduce torrent candidates
- Add MultiSelect dropdowns populated from target instances
- Aggregate categories/tags from all selected target instances

Closes #730
@coderabbitai

coderabbitai Bot commented Dec 12, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds RSS source include/exclude filters to cross-seed: DB columns, model fields, API patch fields and application logic, service-level filtering during snapshot construction, and frontend types/UI to edit and persist the filters.

Changes

Cohort / File(s) Summary
API Handler
internal/api/handlers/crossseed.go
Added four optional patch fields (RSSSourceCategories, RSSSourceTags, RSSSourceExcludeCategories, RSSSourceExcludeTags); updated isEmpty() and applyAutomationSettingsPatch() to recognize and apply them.
Database Migration
internal/database/migrations/032_add_rss_source_filters.sql
Added four TEXT columns to cross_seed_settings: rss_source_categories, rss_source_tags, rss_source_exclude_categories, rss_source_exclude_tags with default '[]'.
Data Models & Store
internal/models/crossseed.go
Extended CrossSeedAutomationSettings with four []string fields; updated defaults, GetSettings() and UpsertSettings() to decode/encode and persist these fields; added IndexerName to CrossSeedRunResult.
Service Logic
internal/services/crossseed/service.go
buildAutomationSnapshots now accepts settings; added matchesRSSSourceFilters() and applied RSS-source filtering to candidate torrents during snapshot construction; propagated IndexerName into run results.
Frontend Types
web/src/types/index.ts
Added rssSourceCategories, rssSourceTags, rssSourceExcludeCategories, rssSourceExcludeTags to CrossSeedAutomationSettings and optional variants in the patch type; added indexerName to run result type.
Frontend Component
web/src/pages/CrossSeedPage.tsx
Aggregates RSS source metadata across target instances; added UI controls and form wiring for include/exclude categories and tags; includes fields in patch payload and updates recent-RSS run UI.
API Docs
internal/web/swagger/openapi.yaml
Added optional indexerName field to CrossSeedRunResult schema.

Sequence Diagram(s)

sequenceDiagram
    participant Frontend
    participant API
    participant Service
    participant DB

    Frontend->>API: PATCH /cross-seed/settings (rssSource* fields)
    API->>DB: Upsert cross_seed_settings (encode rss_source_* JSON)
    DB-->>API: OK
    API-->>Frontend: 200 OK (updated settings)

    note right of Service: Snapshot build during automation run
    Service->>DB: GetSettings (decode rss_source_* JSON)
    DB-->>Service: CrossSeedAutomationSettings (with rssSource* lists)
    Service->>Service: buildAutomationSnapshots(settings)
    Service->>Service: matchesRSSSourceFilters(torrent, settings)
    Service->>Service: filter candidate torrents per snapshot
    Service->>DB: record run results (include indexerName)
    DB-->>Service: OK
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Check isEmpty() / applyAutomationSettingsPatch() nil vs empty-slice semantics.
  • Verify encodeStringSlice / decodeStringSlice and DB JSON defaults handle empty arrays.
  • Review matchesRSSSourceFilters() for include/exclude precedence and case handling.
  • Inspect snapshot integration to ensure settings are passed without side effects.

Possibly related PRs

Suggested labels

api, rss

Poem

🐰 I nibble through code and tidy each burrow,
Four filters tucked in, hopping soft and thorough.
Categories, tags — include or exclude,
Streams now sorted along a neat route,
Hop, run, match — automation's ready to burrow!

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes one out-of-scope change: adding IndexerName field to CrossSeedRunResult, which is not part of the RSS filtering feature request in issue #730. Remove the IndexerName field additions from CrossSeedRunResult across all files (models, handlers, OpenAPI spec) or document why this metadata enhancement is necessary for the RSS filtering feature.
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(crossseed): add RSS source filters for categories and tags' directly and clearly summarizes the main changes in the PR, which add RSS source filtering capabilities for categories and tags.
Linked Issues check ✅ Passed The PR fully implements the feature request from issue #730 by adding include/exclude filtering for tags and categories in RSS Automation, with UI controls and backend filtering logic.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/crossseed-rss-source-filters

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/services/crossseed/service.go (1)

1913-1989: Guard against nil instance deref in new debug logging + normalize filter inputs before use.

log.Debug().Str("instanceName", snap.instance.Name) (Line 1981) assumes snap.instance != nil. If instanceStore.Get can ever return (nil, nil), this panics.

Also, filtering uses raw settings.RSSSource* values; consider trimming/deduping once (ideally in validateAndNormalizeSettings) to avoid whitespace-caused misses.

@@
-            if len(torrents) != originalCount {
-                log.Debug().
-                    Int("instanceID", instanceID).
-                    Str("instanceName", snap.instance.Name).
+            if len(torrents) != originalCount {
+                instanceName := ""
+                if snap.instance != nil {
+                    instanceName = snap.instance.Name
+                }
+                log.Debug().
+                    Int("instanceID", instanceID).
+                    Str("instanceName", instanceName).
                     Int("original", originalCount).
                     Int("filtered", len(torrents)).
                     Msg("RSS source filters reduced torrent candidates")
             }
🧹 Nitpick comments (7)
internal/database/migrations/032_add_rss_source_filters.sql (1)

1-8: Consider adding JSON validity CHECKs for these TEXT “JSON array” columns.
Right now any non-JSON string could be written (manually or via a buggy client), and json.Unmarshal in the app will fail hard on read. If you want stronger safety, add CHECK (json_valid(rss_source_categories)) (and similarly for the other three) where supported.

web/src/pages/CrossSeedPage.tsx (3)

380-391: Normalize rssSource lists on load.*
Right now settings.rssSourceCategories ?? [] etc. are taken verbatim; if older data (or manual edits) contain whitespace/duplicates, the MultiSelect UX can get weird (duplicate pills, partial unselect). Suggest normalizeStringList(...) for each of the four arrays here.


831-871: Consider sorting tag/category option labels for stable UX across instance ordering.
Set insertion order depends on iteration order of the selected instances; sorting rssSourceTagNames (and maybe ensuring category keys are stable) avoids the dropdown “jumping” when instance selection order changes.


1253-1339: UI controls are clear; consider normalizing onChange to keep store data clean.
For the four RSS source MultiSelects, you currently store values as-is. Even though the component avoids duplicates during selection, normalizing (normalizeStringList(values)) would keep persisted settings resilient to casing/whitespace drift and future UI changes.

internal/api/handlers/crossseed.go (2)

164-255: Recommend normalizing RSSSource values when applying the patch (trim/dedup, maybe exclude-wins).*
Right now these arrays are accepted verbatim; it’s easy to end up persisting values like " movies " or duplicates, and it’s unclear what should happen if the same value appears in include+exclude. Consider normalizing at this boundary (or in the service/store) to keep persisted state predictable.


596-633: PUT /cross-seed/settings doesn’t include the new RSS source filter fields.
If PUT is intended to be a complete replacement/update API, consider extending automationSettingsRequest + UpdateAutomationSettings() to include these 4 fields as well (otherwise it’s patch-only).

internal/models/crossseed.go (1)

395-540: Upsert column list/placeholder count/args ordering look consistent.
Optional improvement: normalize these four slices before encoding (trim/dedup) so the DB never stores junk values (helps when other clients write settings besides the web UI).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d44058f and 97e67ee.

📒 Files selected for processing (6)
  • internal/api/handlers/crossseed.go (3 hunks)
  • internal/database/migrations/032_add_rss_source_filters.sql (1 hunks)
  • internal/models/crossseed.go (11 hunks)
  • internal/services/crossseed/service.go (5 hunks)
  • web/src/pages/CrossSeedPage.tsx (7 hunks)
  • web/src/types/index.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-28T20:32:30.126Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/crossseed/service.go:209-212
Timestamp: 2025-11-28T20:32:30.126Z
Learning: Repo: autobrr/qui PR: 641
File: internal/services/crossseed/service.go
Learning: The cross-seed recheck-resume worker intentionally runs for the process lifetime and keys pending entries by hash only. This is acceptable under the current constraint that background seeded-search runs operate on a single instance at a time; graceful shutdown and instanceID|hash keying are deferred by design.

Applied to files:

  • internal/models/crossseed.go
  • internal/services/crossseed/service.go
📚 Learning: 2025-11-27T15:33:39.007Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/jackett/service.go:309-330
Timestamp: 2025-11-27T15:33:39.007Z
Learning: In internal/services/jackett/service.go, synchronous execution paths (test executors, interactive searches with explicit indexer selection, and scheduler-unavailable fallback) intentionally pass jobID=0 to result callbacks. This is acceptable because outcome tracking via ReportIndexerOutcome is only used for async cross-seed operations that always use the scheduler and receive unique jobIDs. Interactive searches and tests do not use outcome tracking.

Applied to files:

  • internal/models/crossseed.go
🧬 Code graph analysis (1)
web/src/pages/CrossSeedPage.tsx (2)
web/src/components/torrents/CategoryTree.tsx (2)
  • buildCategoryTree (59-114)
  • CategoryNode (26-34)
web/src/components/ui/multi-select.tsx (1)
  • MultiSelect (26-170)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run tests
🔇 Additional comments (11)
web/src/pages/CrossSeedPage.tsx (3)

52-62: Nice: form state cleanly separates RSS “target instances/indexers” from RSS “source filters”.
The naming/doc comment makes intent clear (“filter which local torrents to search”).


99-109: Defaults look correct (empty arrays => no filtering).
Matches the PR behavior contract and avoids null/undefined footguns in the UI.


478-504: Patch builder wiring looks consistent with the backend patch semantics.
You’re always sending these arrays (rather than omitting), which is fine since this “Save RSS automation settings” action is effectively a full-save of the automation section.

internal/api/handlers/crossseed.go (2)

47-77: Patch request additions look correct and consistent with existing pointer-based patching.
Using *[]string preserves “omitted vs set to empty array” semantics.


135-162: isEmpty() updated correctly to account for the new fields.
This prevents silent no-op PATCHes when a client only updates RSS source filters.

web/src/types/index.ts (2)

1403-1434: Types align with the backend contract and keep arrays non-null on the read model.
Good match for “empty array means all”.


1436-1465: Patch type shape is correct (optional arrays).
Supports “clear” by sending [], and “no change” by omitting.

internal/models/crossseed.go (3)

18-64: Model field additions are clear and correctly documented (“LOCAL torrents”, empty means all).
Naming matches API/UI (rssSource*).


75-112: Defaults are set to empty slices (good for JSON + UI), and semantics are documented inline.


271-393: GetSettings read path looks correct (select/scan/decode for all 4 columns).
decodeStringSlice fallback to [] preserves the “no filtering” contract.

internal/services/crossseed/service.go (1)

1539-1542: Good propagation of settings into snapshot building (keeps filtering concerns localized).

Comment thread internal/services/crossseed/service.go
Comment thread web/src/pages/CrossSeedPage.tsx
@s0up4200

This comment was marked as resolved.

- Add indexerName field to CrossSeedRunResult to show which indexer
  releases came from in the RSS run details UI
- Fix tag matching to be case-sensitive (Movies != movies) to match
  qBittorrent's actual behavior in matchesSearchFilters,
  matchesCompletionFilters, and hasCrossSeedTag
- Improve RSS runs UI with expandable rows showing instance and indexer
  badges alongside matched torrent names

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/services/crossseed/service.go (1)

6434-6463: Case-sensitive tag matching requires confirmation or case normalization on save.

matchesSearchFilters, matchesCompletionFilters, and hasCrossSeedTag now enforce strict case-sensitive equality for tag comparisons. qBittorrent treats tags as case-sensitive, so this behavior is correct; however, any existing persisted settings with tags in different casings (e.g., "Cross-Seed" vs "cross-seed") will silently stop matching. Consider normalizing tag casing on save to maintain backward compatibility with existing configurations, or document this as a breaking change requiring users to update their tag references.

Also applies to: 6506-6545, 6547-6554

♻️ Duplicate comments (2)
web/src/pages/CrossSeedPage.tsx (1)

327-356: Aggregation should tolerate per-instance failures + stabilize queryKey ordering.
Promise.all means one instance failing categories/tags breaks the whole dropdown population, and the array-based queryKey will refetch when the same IDs are selected in a different order.

+  const sortedRssTargetInstanceIds = useMemo(
+    () => [...automationForm.targetInstanceIds].sort((a, b) => a - b),
+    [automationForm.targetInstanceIds]
+  )
+
   // Fetch categories/tags from all RSS Automation target instances (aggregated)
   const { data: rssSourceMetadata } = useQuery({
-    queryKey: ["cross-seed", "rss-source-metadata", automationForm.targetInstanceIds],
+    queryKey: ["cross-seed", "rss-source-metadata", sortedRssTargetInstanceIds],
     queryFn: async () => {
-      if (automationForm.targetInstanceIds.length === 0) return null
-      const results = await Promise.all(
-        automationForm.targetInstanceIds.map(async (instanceId) => {
-          const [categories, tags] = await Promise.all([
-            api.getCategories(instanceId),
-            api.getTags(instanceId),
-          ])
-          return { categories, tags }
-        })
-      )
+      if (sortedRssTargetInstanceIds.length === 0) return null
+      const settled = await Promise.allSettled(
+        sortedRssTargetInstanceIds.map(async (instanceId) => {
+          const [categories, tags] = await Promise.all([
+            api.getCategories(instanceId),
+            api.getTags(instanceId),
+          ])
+          return { instanceId, categories, tags }
+        })
+      )
+      const results = settled
+        .filter((r): r is PromiseFulfilledResult<{ instanceId: number; categories: any; tags: string[] }> => r.status === "fulfilled")
+        .map(r => r.value)
 
       // Aggregate categories and tags from all instances
       const allCategories: Record<string, { name: string; savePath: string }> = {}
       const allTags = new Set<string>()
-      for (const result of results) {
+      for (const result of results) {
         for (const [name, cat] of Object.entries(result.categories)) {
           allCategories[name] = cat
         }
         for (const tag of result.tags) {
           allTags.add(tag)
         }
       }
-      return { categories: allCategories, tags: Array.from(allTags) }
+      return { categories: allCategories, tags: Array.from(allTags) }
     },
-    enabled: automationForm.targetInstanceIds.length > 0,
+    enabled: sortedRssTargetInstanceIds.length > 0,
     staleTime: 5 * 60 * 1000,
   })
internal/services/crossseed/service.go (1)

6465-6504: Fix matchesRSSSourceFilters nil-settings contract (nil should mean “no filtering”).
The comment says empty filters mean “all”, but settings == nil currently returns false, which is a surprising helper contract and can cause accidental “filter everything out” if reused.

func matchesRSSSourceFilters(torrent *qbt.Torrent, settings *models.CrossSeedAutomationSettings) bool {
-    if torrent == nil || settings == nil {
-        return false
-    }
+    if torrent == nil {
+        return false
+    }
+    if settings == nil {
+        return true // no filters configured
+    }
🧹 Nitpick comments (6)
web/src/pages/CrossSeedPage.tsx (2)

835-875: Sort/dedupe aggregated tag & category options for stable UX.
Right now the Set/iteration order can make the dropdown jump around between renders/instance selections.

-  const rssSourceTagNames = useMemo(() => rssSourceMetadata?.tags ?? [], [rssSourceMetadata])
+  const rssSourceTagNames = useMemo(
+    () => (rssSourceMetadata?.tags ?? []).slice().sort((a, b) => a.localeCompare(b)),
+    [rssSourceMetadata]
+  )

(Also consider sorting flattened before returning in rssSourceCategorySelectOptions if tree order isn’t guaranteed by input.)


1343-1548: Runs panel is readable, but the Scheduled/Manual/Other render blocks are largely duplicated.
A small RunGroup + RunRow component (or a map over {key,label,icon,iconClass}) would reduce repetition and future bug drift.

internal/models/crossseed.go (2)

31-37: Consider normalizing filter inputs before persisting (trim/dedupe)
Right now whatever the UI sends is stored verbatim; it’s easy to end up with duplicates or whitespace-only entries. You already have normalizeStringSlice (Line 1292+); consider applying it to RSSSource* (and possibly other tag/category slices) in UpsertSettings before encoding.


76-111: Avoid double time.Now() to prevent subtle CreatedAt/UpdatedAt skew
CreatedAt and UpdatedAt are set via separate time.Now().UTC() calls (Line 109-110). Prefer a single now := time.Now().UTC() for consistency.

internal/services/crossseed/service.go (2)

1619-1666: Make IndexerName consistent: use the resolved sourceIndexer instead of result.Indexer in run results.
You already resolve sourceIndexer via GetIndexerNameFromInfo (Line 1620-1623), but several CrossSeedRunResult{ IndexerName: ... } assignments still use result.Indexer, which can diverge from what you send in CrossSeedRequest.IndexerName.

- IndexerName:  result.Indexer,
+ IndexerName:  sourceIndexer,

(Apply similarly to the exists pre-check result blocks and the “no_match”/“dry-run”/“no_result” cases.)

Also applies to: 1695-1705, 1752-1762, 1833-1893


1919-2002: RSS source filtering in snapshot build looks correct; consider normalizing filter slices once.
The overall flow (compute hasRSSSourceFilters, filter per instance, log reduction) is solid. If these lists can get large, converting settings slices to map[string]struct{} once per run would avoid repeated slices.Contains in tight loops.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f48e2ca and 795d779.

📒 Files selected for processing (5)
  • internal/models/crossseed.go (11 hunks)
  • internal/services/crossseed/service.go (15 hunks)
  • internal/web/swagger/openapi.yaml (1 hunks)
  • web/src/pages/CrossSeedPage.tsx (9 hunks)
  • web/src/types/index.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/src/types/index.ts
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/crossseed/service.go:209-212
Timestamp: 2025-11-28T20:32:30.126Z
Learning: Repo: autobrr/qui PR: 641
File: internal/services/crossseed/service.go
Learning: The cross-seed recheck-resume worker intentionally runs for the process lifetime and keys pending entries by hash only. This is acceptable under the current constraint that background seeded-search runs operate on a single instance at a time; graceful shutdown and instanceID|hash keying are deferred by design.
📚 Learning: 2025-11-28T20:32:30.126Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/crossseed/service.go:209-212
Timestamp: 2025-11-28T20:32:30.126Z
Learning: Repo: autobrr/qui PR: 641
File: internal/services/crossseed/service.go
Learning: The cross-seed recheck-resume worker intentionally runs for the process lifetime and keys pending entries by hash only. This is acceptable under the current constraint that background seeded-search runs operate on a single instance at a time; graceful shutdown and instanceID|hash keying are deferred by design.

Applied to files:

  • internal/services/crossseed/service.go
  • internal/models/crossseed.go
📚 Learning: 2025-12-11T08:39:55.579Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 746
File: internal/services/reannounce/service.go:480-481
Timestamp: 2025-12-11T08:39:55.579Z
Learning: In autobrr/qui's internal/services/reannounce/service.go, the hasHealthyTracker, getProblematicTrackers, and getHealthyTrackers functions intentionally match qbrr's lenient tracker health logic (skip unregistered trackers and check if any other tracker is healthy) rather than go-qbittorrent's strict isTrackerStatusOK logic (which treats unregistered as an immediate failure). For multi-tracker torrents, if one tracker is working, reannouncing won't help. The duplication of the health check logic across these three functions is acceptable as it's a simple one-liner, and extracting it would add unnecessary complexity.

Applied to files:

  • internal/services/crossseed/service.go
📚 Learning: 2025-11-21T21:11:50.633Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 625
File: internal/qbittorrent/sync_manager.go:1112-1123
Timestamp: 2025-11-21T21:11:50.633Z
Learning: In internal/qbittorrent/sync_manager.go, the GetCachedFilesBatch interface documentation (around line 39-40) should specify "uppercase hex" instead of "lowercase hex" to match the actual normalization practice used throughout the codebase (e.g., normalizeHash in internal/services/crossseed/service.go uses strings.ToUpper, and hash filtering uses uppercase).

Applied to files:

  • internal/services/crossseed/service.go
📚 Learning: 2025-11-27T15:33:39.007Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/jackett/service.go:309-330
Timestamp: 2025-11-27T15:33:39.007Z
Learning: In internal/services/jackett/service.go, synchronous execution paths (test executors, interactive searches with explicit indexer selection, and scheduler-unavailable fallback) intentionally pass jobID=0 to result callbacks. This is acceptable because outcome tracking via ReportIndexerOutcome is only used for async cross-seed operations that always use the scheduler and receive unique jobIDs. Interactive searches and tests do not use outcome tracking.

Applied to files:

  • internal/services/crossseed/service.go
🧬 Code graph analysis (3)
internal/services/crossseed/service.go (2)
internal/models/crossseed.go (1)
  • CrossSeedAutomationSettings (20-64)
web/src/types/index.ts (3)
  • CrossSeedAutomationSettings (1404-1435)
  • Torrent (226-283)
  • Category (427-430)
internal/models/crossseed.go (1)
web/src/types/index.ts (2)
  • CrossSeedAutomationSettings (1404-1435)
  • Category (427-430)
web/src/pages/CrossSeedPage.tsx (7)
web/src/lib/api.ts (1)
  • api (1711-1711)
web/src/components/torrents/CategoryTree.tsx (2)
  • buildCategoryTree (59-114)
  • CategoryNode (26-34)
web/src/components/ui/label.tsx (1)
  • Label (29-29)
web/src/components/ui/multi-select.tsx (1)
  • MultiSelect (26-170)
web/src/components/ui/separator.tsx (1)
  • Separator (33-33)
web/src/components/ui/collapsible.tsx (3)
  • Collapsible (14-14)
  • CollapsibleTrigger (14-14)
  • CollapsibleContent (14-14)
web/src/components/ui/badge.tsx (1)
  • Badge (51-51)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run tests
🔇 Additional comments (7)
internal/web/swagger/openapi.yaml (1)

3741-3750: indexerName addition looks consistent with existing CrossSeedRunResult shape.
CamelCase matches the schema (instanceId, instanceName) and nullable: true aligns with other optional fields.

web/src/pages/CrossSeedPage.tsx (1)

55-66: New RSS source filter fields are wired through defaults → load → patch payload correctly.
This is the right shape for “empty means include all / exclude none”, and the persistence path looks consistent.

Also applies to: 103-113, 384-395, 482-507

internal/models/crossseed.go (4)

31-37: Run gofmt: struct field alignment is currently noisy
The PR notes already mention gofmt issues; these new fields add more alignment churn. Please run gofmt on this file so the struct formatting matches repo conventions.


160-170: CrossSeedRunResult.IndexerName addition is fine; verify producers populate it
This struct is (de)serialized via results_json, so the store layer won’t populate it directly—make sure the service layer that constructs run.Results sets IndexerName where intended.


274-370: Settings read path looks consistent (SELECT/Scan/decode), but ensure DB migration guarantees column presence
The SELECT + Scan ordering for the new rss_source_* columns matches the decode calls, so this looks correct. Please confirm the migration for cross_seed_settings adds these columns (and doesn’t break older DBs) since GetSettings will now always select them.


396-535: Upsert placeholder/arg count matches; consider making nil vs empty semantics explicit
The INSERT has 27 placeholders and you pass 27 args—good. Since encodeStringSlice forces nil -> [], you’ve chosen “always an array” semantics; just ensure this matches any PATCH semantics (e.g., omitting a field vs clearing it) upstream.

internal/services/crossseed/service.go (1)

1539-1542: Good: threading automation settings into snapshot build enables correct RSS source filtering.
This change makes the filtering apply “upstream” (before candidate construction), which should reduce work later.

@s0up4200

This comment was marked as resolved.

@s0up4200
s0up4200 merged commit f5ede56 into main Dec 12, 2025
12 checks passed
@s0up4200
s0up4200 deleted the feat/crossseed-rss-source-filters branch December 12, 2025 22:52
alexlebens pushed a commit to alexlebens/infrastructure that referenced this pull request Dec 18, 2025
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/autobrr/qui](https://github.com/autobrr/qui) | minor | `v1.9.1` -> `v1.10.0` |

---

### Release Notes

<details>
<summary>autobrr/qui (ghcr.io/autobrr/qui)</summary>

### [`v1.10.0`](https://github.com/autobrr/qui/releases/tag/v1.10.0)

[Compare Source](autobrr/qui@v1.9.1...v1.10.0)

#### Changelog

##### New Features

- [`f2b17e6`](autobrr/qui@f2b17e6): feat(config): add SESSION\_SECRET\_FILE env var ([#&#8203;661](autobrr/qui#661)) ([@&#8203;undefined-landmark](https://github.com/undefined-landmark))
- [`f5ede56`](autobrr/qui@f5ede56): feat(crossseed): add RSS source filters for categories and tags ([#&#8203;757](autobrr/qui#757)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`9dee7bb`](autobrr/qui@9dee7bb): feat(crossseed): add Unicode normalization for title and file matching ([#&#8203;742](autobrr/qui#742)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`d44058f`](autobrr/qui@d44058f): feat(crossseed): add skip auto-resume settings per mode ([#&#8203;755](autobrr/qui#755)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`9e3534a`](autobrr/qui@9e3534a): feat(crossseed): add webhook source filters for categories and tags ([#&#8203;763](autobrr/qui#763)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`c8bbe07`](autobrr/qui@c8bbe07): feat(crossseed): only poll status endpoints when features are enabled ([#&#8203;738](autobrr/qui#738)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`fda8101`](autobrr/qui@fda8101): feat(sidebar): add size tooltips and deduplicate cross-seed sizes ([#&#8203;724](autobrr/qui#724)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`e4c0556`](autobrr/qui@e4c0556): feat(torrent): add sequential download toggles ([#&#8203;776](autobrr/qui#776)) ([@&#8203;rare-magma](https://github.com/rare-magma))
- [`2a43f15`](autobrr/qui@2a43f15): feat(torrents): autocomplete paths ([#&#8203;634](autobrr/qui#634)) ([@&#8203;rare-magma](https://github.com/rare-magma))
- [`1c07b33`](autobrr/qui@1c07b33): feat(torrents): replace filtered speeds with global ([#&#8203;745](autobrr/qui#745)) ([@&#8203;jabloink](https://github.com/jabloink))
- [`cd0deee`](autobrr/qui@cd0deee): feat(tracker): add per-domain stats inclusion toggle for merged trackers ([#&#8203;781](autobrr/qui#781)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`b6a6200`](autobrr/qui@b6a6200): feat(web): add Size column to Tracker Breakdown table ([#&#8203;770](autobrr/qui#770)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`560071b`](autobrr/qui@560071b): feat(web): add zebra striping to torrent table ([#&#8203;726](autobrr/qui#726)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`f8f65a8`](autobrr/qui@f8f65a8): feat(web): improve auto-search on completion UX ([#&#8203;743](autobrr/qui#743)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`e36312f`](autobrr/qui@e36312f): feat(web): improve torrent selection UX with unified click and escape behavior ([#&#8203;782](autobrr/qui#782)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`27c1daa`](autobrr/qui@27c1daa): feat(web): napster theme ([#&#8203;728](autobrr/qui#728)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`e3950de`](autobrr/qui@e3950de): feat(web): new torrent details panel for desktop ([#&#8203;760](autobrr/qui#760)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`6c66ba5`](autobrr/qui@6c66ba5): feat(web): persist tab state in URL for CrossSeed and Settings pages ([#&#8203;775](autobrr/qui#775)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`59884a9`](autobrr/qui@59884a9): feat(web): share tracker customizations with filtersidebar ([#&#8203;717](autobrr/qui#717)) ([@&#8203;s0up4200](https://github.com/s0up4200))

##### Bug Fixes

- [`fafd278`](autobrr/qui@fafd278): fix(api): add webhook source filter fields to PATCH settings endpoint ([#&#8203;774](autobrr/qui#774)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`bdf0339`](autobrr/qui@bdf0339): fix(api): support apikey query param with custom base URL ([#&#8203;748](autobrr/qui#748)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`c3c8d66`](autobrr/qui@c3c8d66): fix(crossseed): compare Site and Sum fields for anime releases ([#&#8203;769](autobrr/qui#769)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`cb4c965`](autobrr/qui@cb4c965): fix(crossseed): detect file name differences and fix hasExtraSourceFiles ([#&#8203;741](autobrr/qui#741)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`fd9e054`](autobrr/qui@fd9e054): fix(crossseed): fix batch completion searches and remove legacy settings ([#&#8203;744](autobrr/qui#744)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`26706a0`](autobrr/qui@26706a0): fix(crossseed): normalize punctuation in title matching ([#&#8203;718](autobrr/qui#718)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`db30566`](autobrr/qui@db30566): fix(crossseed): rename files before folder to avoid path conflicts ([#&#8203;752](autobrr/qui#752)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`8886ac4`](autobrr/qui@8886ac4): fix(crossseed): resolve category creation race condition and relax autoTMM ([#&#8203;767](autobrr/qui#767)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`f8f2a05`](autobrr/qui@f8f2a05): fix(crossseed): support game scene releases with RAR files ([#&#8203;768](autobrr/qui#768)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`918adee`](autobrr/qui@918adee): fix(crossseed): treat x264/H.264/H264/AVC as equivalent codecs ([#&#8203;766](autobrr/qui#766)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`c4b1f0a`](autobrr/qui@c4b1f0a): fix(dashboard): merge tracker customizations with duplicate displayName ([#&#8203;751](autobrr/qui#751)) ([@&#8203;jabloink](https://github.com/jabloink))
- [`3c6e0f9`](autobrr/qui@3c6e0f9): fix(license): remove redundant validation call after activation ([#&#8203;749](autobrr/qui#749)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`a9c7754`](autobrr/qui@a9c7754): fix(reannounce): simplify tracker detection to match qbrr logic ([#&#8203;746](autobrr/qui#746)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`3baa007`](autobrr/qui@3baa007): fix(rss): skip download when torrent already exists by infohash ([#&#8203;715](autobrr/qui#715)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`55d0ccc`](autobrr/qui@55d0ccc): fix(swagger): respect base URL for API docs routes ([#&#8203;758](autobrr/qui#758)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`47695fd`](autobrr/qui@47695fd): fix(web): add height constraint to filter sidebar wrapper for proper scrolling ([#&#8203;778](autobrr/qui#778)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`4b3bfea`](autobrr/qui@4b3bfea): fix(web): default torrent format to v1 in creator dialog ([#&#8203;723](autobrr/qui#723)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`2d54b79`](autobrr/qui@2d54b79): fix(web): pin submit button in Services sheet footer ([#&#8203;756](autobrr/qui#756)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`2bcd6a3`](autobrr/qui@2bcd6a3): fix(web): preserve folder collapse state during file tree sync ([#&#8203;740](autobrr/qui#740)) ([@&#8203;ewenjo](https://github.com/ewenjo))
- [`57f3f1d`](autobrr/qui@57f3f1d): fix(web): sort Peers column by total peers instead of connected ([#&#8203;759](autobrr/qui#759)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`53a8818`](autobrr/qui@53a8818): fix(web): sort Seeds column by total seeds instead of connected ([#&#8203;747](autobrr/qui#747)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`d171915`](autobrr/qui@d171915): fix(web): sort folders before files in torrent file tree ([#&#8203;764](autobrr/qui#764)) ([@&#8203;s0up4200](https://github.com/s0up4200))

##### Other Changes

- [`172b4aa`](autobrr/qui@172b4aa): chore(assets): replace napster.svg with napster.png for logo update ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`dc83102`](autobrr/qui@dc83102): chore(deps): bump the github group with 3 updates ([#&#8203;761](autobrr/qui#761)) ([@&#8203;dependabot](https://github.com/dependabot)\[bot])
- [`75357d3`](autobrr/qui@75357d3): chore: fix napster logo ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`206c4b2`](autobrr/qui@206c4b2): refactor(web): extract CrossSeed completion to accordion component ([#&#8203;762](autobrr/qui#762)) ([@&#8203;s0up4200](https://github.com/s0up4200))

**Full Changelog**: <autobrr/qui@v1.9.1...v1.10.0>

#### Docker images

- `docker pull ghcr.io/autobrr/qui:v1.10.0`
- `docker pull ghcr.io/autobrr/qui:latest`

#### What to do next?

- Join our [Discord server](https://discord.autobrr.com/qui)

Thank you for using qui!

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4zOS4xIiwidXBkYXRlZEluVmVyIjoiNDIuMzkuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW1hZ2UiXX0=-->

Reviewed-on: https://gitea.alexlebens.dev/alexlebens/infrastructure/pulls/2664
Co-authored-by: Renovate Bot <renovate-bot@alexlebens.net>
Co-committed-by: Renovate Bot <renovate-bot@alexlebens.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] Exclude tags from RSS Automation Searches

1 participant