Add interactive release search (M6)
Release listing and grab in the shared arr client, a full-screen release list (sort, hide rejected, rejection reasons, confirmation before grabbing rejected releases; Usenet only), and a season/episode picker for Sonarr. Overlays are now a stack so dialogs can open above the release list. Add a fake Newznab indexer to the dev setup. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -35,3 +35,18 @@ curl -H "X-Api-Key: devradarrapikey00000000000000000" -H 'Content-Type: applicat
|
||||
-d '{"title":"The.Matrix.1999.1080p.BluRay.x264-DEV","downloadUrl":"http://host.containers.internal:19191/some.nzb","protocol":"usenet","publishDate":"2026-01-01T00:00:00Z"}' \
|
||||
http://127.0.0.1:17878/api/v3/release/push
|
||||
```
|
||||
|
||||
## Interactive search without a real indexer
|
||||
|
||||
`dev/fake-indexer.py` is a tiny Newznab indexer that returns a fixed set of releases for "The Matrix (1999)"
|
||||
and "Breaking Bad"; Radarr/Sonarr still do their own parsing and approve/reject decisions. Its NZBs are
|
||||
dummies, so grabbed releases just sit in the SABnzbd queue.
|
||||
|
||||
```sh
|
||||
python3 dev/fake-indexer.py & # listens on :19292
|
||||
dev/add-indexer.sh # registers it in the dev Radarr and Sonarr
|
||||
```
|
||||
|
||||
Add the movie / series to the dev instances first (e.g. from the TUI), then press `i` on it.
|
||||
To test with a real indexer, add it in the dev Radarr/Sonarr web UI (Settings, Indexers) instead of pasting
|
||||
credentials anywhere else.
|
||||
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
# Register dev/fake-indexer.py as an indexer in the dev Radarr and Sonarr (idempotent).
|
||||
# Start the fake indexer first: python3 dev/fake-indexer.py
|
||||
set -euo pipefail
|
||||
|
||||
add() { # base key categories
|
||||
local base=$1 key=$2 cats=$3
|
||||
if curl -sf -H "X-Api-Key: $key" "$base/api/v3/indexer" | grep -q '"name": "Fake indexer"'; then return 0; fi
|
||||
curl -sf -H "X-Api-Key: $key" -H 'Content-Type: application/json' "$base/api/v3/indexer" -d "{
|
||||
\"name\":\"Fake indexer\",\"implementation\":\"Newznab\",\"configContract\":\"NewznabSettings\",
|
||||
\"enableRss\":true,\"enableAutomaticSearch\":true,\"enableInteractiveSearch\":true,\"protocol\":\"usenet\",\"priority\":25,\"tags\":[],
|
||||
\"fields\":[{\"name\":\"baseUrl\",\"value\":\"http://host.containers.internal:19292\"},{\"name\":\"apiPath\",\"value\":\"/api\"},
|
||||
{\"name\":\"apiKey\",\"value\":\"dev\"},{\"name\":\"categories\",\"value\":$cats}]}" >/dev/null
|
||||
echo "added indexer to $base"
|
||||
}
|
||||
|
||||
add http://127.0.0.1:17878 devradarrapikey00000000000000000 '[2000,2040,2045]'
|
||||
add http://127.0.0.1:18989 devsonarrapikey00000000000000000 '[5000,5040]'
|
||||
Executable
+91
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env python3
|
||||
"""A tiny fake Newznab indexer for the dev instances (no real indexer needed).
|
||||
|
||||
Answers every movie / TV search with a fixed set of releases for "The Matrix (1999)" and
|
||||
"Breaking Bad", and serves a dummy NZB for all of them. Radarr/Sonarr then do their real
|
||||
release parsing and decisions (approved / rejected, with reasons) on top.
|
||||
|
||||
python3 dev/fake-indexer.py # listens on 0.0.0.0:19292
|
||||
dev/add-indexer.sh # registers it in the dev Radarr and Sonarr
|
||||
"""
|
||||
import time
|
||||
from email.utils import formatdate
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
from xml.sax.saxutils import escape
|
||||
|
||||
PORT = 19292
|
||||
HOST = "host.containers.internal" # how the dev containers reach this machine
|
||||
|
||||
GB = 1024 ** 3
|
||||
MOVIES = [ # title, size, age in days
|
||||
("The.Matrix.1999.1080p.BluRay.x264-DEV", 8 * GB, 1),
|
||||
("The.Matrix.1999.2160p.UHD.BluRay.x265-DEV", 40 * GB, 3),
|
||||
("The.Matrix.1999.720p.BluRay.x264-OTHER", 4 * GB, 400),
|
||||
("The.Matrix.1999.480p.DVDRip.XviD-OLD", 700 * 1024 ** 2, 2000),
|
||||
("The.Matrix.Reloaded.2003.1080p.BluRay.x264-DEV", 9 * GB, 5),
|
||||
]
|
||||
TV = [
|
||||
("Breaking.Bad.S01E01.1080p.BluRay.x264-DEV", 2 * GB, 30),
|
||||
("Breaking.Bad.S01E01.720p.HDTV.x264-OTHER", 900 * 1024 ** 2, 60),
|
||||
("Breaking.Bad.S01.1080p.BluRay.x264-DEV", 14 * GB, 20),
|
||||
("Breaking.Bad.S02E01.1080p.BluRay.x264-DEV", 2 * GB, 30),
|
||||
]
|
||||
|
||||
NZB = b"""<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE nzb PUBLIC "-//newzBin//DTD NZB 1.1//EN" "http://www.newzbin.com/DTD/nzb/nzb-1.1.dtd">
|
||||
<nzb xmlns="http://www.newzbin.com/DTD/2003/nzb">
|
||||
<file poster="a@b.c" date="1700000000" subject="dummy [1/1] - "dummy.mkv" yEnc (1/10)">
|
||||
<groups><group>alt.binaries.test</group></groups>
|
||||
<segments><segment bytes="500000" number="1">abc123@example.com</segment></segments>
|
||||
</file>
|
||||
</nzb>
|
||||
"""
|
||||
|
||||
CAPS = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<caps><server version="1.0" title="arr-tui fake indexer"/><limits max="100" default="100"/>
|
||||
<searching><search available="yes" supportedParams="q"/>
|
||||
<tv-search available="yes" supportedParams="q,season,ep,tvdbid"/>
|
||||
<movie-search available="yes" supportedParams="q,imdbid,tmdbid"/></searching>
|
||||
<categories><category id="2000" name="Movies"><subcat id="2040" name="Movies/HD"/><subcat id="2045" name="Movies/UHD"/></category>
|
||||
<category id="5000" name="TV"><subcat id="5040" name="TV/HD"/></category></categories></caps>"""
|
||||
|
||||
|
||||
def item(n, title, size, age_days, cat):
|
||||
link = f"http://{HOST}:{PORT}/nzb/{n}.nzb"
|
||||
date = formatdate(time.time() - age_days * 86400)
|
||||
return f"""<item><title>{escape(title)}</title><guid isPermaLink="true">http://{HOST}:{PORT}/details/{n}</guid>
|
||||
<link>{link}</link><pubDate>{date}</pubDate><enclosure url="{link}" length="{size}" type="application/x-nzb"/>
|
||||
<newznab:attr name="category" value="{cat}"/><newznab:attr name="size" value="{size}"/></item>"""
|
||||
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def log_message(self, *args):
|
||||
pass
|
||||
|
||||
def reply(self, body, ctype="application/xml"):
|
||||
data = body if isinstance(body, bytes) else body.encode()
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", ctype)
|
||||
self.send_header("Content-Length", str(len(data)))
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
|
||||
def do_GET(self):
|
||||
url = urlparse(self.path)
|
||||
if url.path.startswith("/nzb/"):
|
||||
return self.reply(NZB, "application/x-nzb")
|
||||
q = {k: v[0] for k, v in parse_qs(url.query).items()}
|
||||
t = q.get("t", "")
|
||||
if t == "caps":
|
||||
return self.reply(CAPS)
|
||||
releases = TV if t == "tvsearch" else MOVIES
|
||||
cat = 5040 if t == "tvsearch" else 2040
|
||||
items = "".join(item(i, *r, cat) for i, r in enumerate(releases))
|
||||
self.reply(f"""<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0" xmlns:newznab="http://www.newznab.com/DTD/2010/feeds/attributes/"><channel><title>fake</title>{items}</channel></rss>""")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(f"fake indexer on :{PORT}")
|
||||
HTTPServer(("0.0.0.0", PORT), Handler).serve_forever()
|
||||
Reference in New Issue
Block a user