Skip to content

Prowlarr: grab search results to the download client - #101

Open
ndandan wants to merge 2 commits into
Shoshuo:mainfrom
ndandan:pr/prowlarr-grab
Open

Prowlarr: grab search results to the download client#101
ndandan wants to merge 2 commits into
Shoshuo:mainfrom
ndandan:pr/prowlarr-grab

Conversation

@ndandan

@ndandan ndandan commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Fixes #71.

Manual search results on the Prowlarr page get an Actions column with a Grab button per release, plus tracker links on the titles (the search payload already carried infoUrl unused — same idea as #35).

  • ProwlarrClient::grab(guid, indexerId) POSTs Prowlarr's own /api/v1/search grab endpoint via the existing requestWithError() pattern, so the release is sent to whatever download client the indexer is configured with in Prowlarr.
  • New POST /prowlarr/grab route (admin-gated like the rest of the controller): 400 {ok:false, error:"invalid_request"} on missing/invalid input, otherwise the client result passes through verbatim, and the UI toasts success or the server's error string.
  • One delegated click listener on the results container (rows are rebuilt per search, so no per-row bindings); the button disables in flight, re-enables on failure, shows ✓ on success.
  • Hardening that this change made necessary: the page-local esc() helper now escapes " (it previously didn't, and guid/infoUrl are indexer-controlled values newly interpolated into attribute contexts), and infoUrl goes through an http(s)-only allowlist in the search mapping.

Tests: controller validation + passthrough (ProwlarrGrabTest, asserts the client is never called on invalid input) and the URL allowlist (ProwlarrSafeInfoUrlTest). 11 tests / 22 assertions green, lint:twig clean, EN+FR keys included. Verified against a live Prowlarr instance.

🤖 Generated with Claude Code

ndandan and others added 2 commits August 22, 2026 20:03
…links — upstream Shoshuo#71 Shoshuo#35

Adds a Grab action to Prowlarr manual-search results and makes the result
title a link to the tracker's detail page when Prowlarr provides one.

- ProwlarrClient::grab(guid, indexerId) POSTs to /api/v1/search, the same
  endpoint Prowlarr's own UI uses to route a grab to the indexer's
  configured download client (no client picker on our side).
- New POST /prowlarr/grab route (prowlarr_grab) validates guid/indexerId
  and 400s with {ok:false,error:'invalid_request'} before touching the
  upstream client; otherwise returns ProwlarrClient::grab()'s result
  verbatim.
- doSearch() rows gain an Actions column (grab button) and wrap the title
  in an <a href="infoUrl" target="_blank" rel="noopener"> when the indexer
  supplied one (folding in Shoshuo#35's spirit for this page). One delegated
  click listener on the results container handles the grab request,
  toasting success/failure and re-enabling the button on failure.
- New prowlarr.search.* keys in both locale files (grab, grab_sent,
  grab_failed_tpl); reuses the existing prowlarr.common.actions key for
  the new column header.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cker links

Fix-round-1 for the Shoshuo#71 grab-action commit:

- esc() (prowlarr/index.html.twig) escaped only & < > via the
  textContent->innerHTML round-trip. That's fine for text nodes but this
  page also interpolates esc() output into attributes (href, data-guid,
  data-indexer-id) — a `"` in indexer-supplied guid/infoUrl broke out of
  the attribute. Appends .replace(/"/g, '&quot;') to the existing helper:
  behavior-preserving for text contexts, closes the attribute-injection
  gap for the new grab button + tracker link.
- ProwlarrClient::search() now maps infoUrl through a new private static
  safeInfoUrl(), only passing through http(s) URLs (case-insensitive
  scheme match) — this fork's CSP blocks javascript: navigation, but this
  commit becomes a standalone upstream PR and upstream has no such CSP.
  Mirrors the scheme-allowlist pattern used by the parallel Radarr/Sonarr
  infoUrl task.

Adds ProwlarrSafeInfoUrlTest (reflection-invoked, same convention as
ClientErrorExtractionTest since the guard is private static): rejects
javascript:/data:/scheme-relative/non-string/empty, keeps http(s)
including an uppercase-scheme URL.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Take action on Prowlarr search

1 participant