Skip to main content

Indexer service pipeline

The indexer service watches the monitor table and searches torrent indexers for pending episodes and seasons. When it finds a match it inserts a download queue entry for the downloader service to pick up.

Startup

  1. Database connection initialised (same PostgreSQL as core).
  2. Provider packages self-register via init() (kbdex and Prowlarr are blank-imported in cmd/indexer/main.go).
  3. PollAndQueue starts in a background goroutine.
  4. Health/test HTTP server starts on port 8082.

Poll loop

Runs every prowlarrInterval seconds (default 10 s).

Timer fires


Query DB for one pending monitor
WHERE monitored = true
AND available = false
AND (status = 'pending'
OR status = 'searching' for > 10 min)
ORDER BY is_season DESC ← season packs searched first


Mark monitor status = 'searching'


Build SearchRequest {title, alternate_titles, season, episode, library_id, ...}


GetEnabled(db) → active providers (each enabled via its kbdexEnabled / prowlarrEnabled toggle; results from all are merged)


Provider.Search(ctx, db, req) [see provider detail below]


buildMatchLog → filter by title similarity + season + episode


pickBest → quality rank + seeders sort, blacklist check

├── match found → insert download_queue row, set monitor status = 'queued'
└── no match → set monitor status = 'pending' (retry next cycle)

Provider: kbdex

kbdex is an AniDB-aware search service. It handles title resolution and episode filtering server-side.

SearchRequest


lookupAnidbID: SELECT source_id FROM media WHERE id = library_id


GET {kbdexUrl}/search?anidb_id=N[&season=S][&episode=E]
│ Results cached on disk keyed by "kbdex:{id}:s{S}:e{E}"
│ Cache TTL = prowlarrCacheAge

[]TorrentResult (already filtered by kbdex server-side via anitopy)

Provider: Prowlarr

Prowlarr is a generic indexer aggregator. kbarr queries it with multiple title variants and merges results.

SearchRequest


Build query variants from title + alternate titles
├── Season searches: "Title S01", "Title Season 1", "Title Batch", ...
└── Episode searches: "Title S01E05", "Title - 05", ...


For each variant: GET {prowlarrUrl}/api/v1/search?query=...&apikey=...
│ Each variant result set cached on disk (prowlarrCacheAge TTL)

Merge + deduplicate by DownloadURL


[]TorrentResult

Client-side filtering (shared by both providers)

After the provider returns results, buildMatchLog and pickBest apply a second pass:

buildMatchLog — runs each torrent filename through anitogo (Go torrent filename parser), then checks:

  1. Title similarity ≥ matchThreshold (default 80%) using Sørensen–Dice bigrams
  2. Parsed season matches the monitor's season
  3. For season packs: rejects individual episode releases
  4. For episodes: parsed episode matches exactly

pickBest — from passing results:

  1. Removes blacklisted torrents
  2. Drops results above the preferredQuality cap (e.g. drops 4K if preference is 1080p)
  3. Sorts by quality rank, subtitle language, release-group preference, and seeder count (order depends on releaseGroupPriorityMode)
  4. Returns the top entry

Release group preference ranking

Two settings control release-group-based ordering:

SettingTypeDefaultDescription
releaseGroupPreferenceOrdercomma-separated stringAll 11 canonical groupsOrdered list of preferred release groups. First = most preferred. Anime Time is accepted as an alias for ASW.
releaseGroupPriorityModeenumtie-breakHow strongly the group preference is applied.

Tie-break mode (default): Compares the configured group order only when quality rank and subtitle language are equal, acting as a tiebreaker before seeders. Releases from preferred groups are favoured but do not outrank a higher-quality release or one with a matching subtitle.

Strong mode: Compares the configured group order immediately after the quality-cap gate, before quality rank itself. A preferred-group release can outrank a higher-quality release from a non-preferred group. The quality cap still applies (e.g. 4K excluded if preference is 1080p).

Fallback: When no candidate matches any configured group or the preference list is empty, the existing quality → subtitle → seeders ordering applies unchanged. Unknown or missing release groups sit after all preferred groups.

Key packages

PackageRole
internal/indexer/provider/Provider interface, registry, shared cache utilities
internal/indexer/provider/kbdex/kbdex provider implementation
internal/indexer/provider/prowlarr/Prowlarr provider implementation
internal/indexer/service/Poll loop, match scoring, download queue insertion
internal/parser/anitogo wrapper with memoisation and Japanese episode fallbacks