Screenshots: Trakt lists, Trakt in the Config view

The fake services gain a Trakt API (port 19894) and a sequel that is not
in the library; a new scene shows the Anticipated list. The other
pictures are retaken (Config has the Trakt section, the search status
bar mentions [ ] lists, the calendar is two days on).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 23:05:35 +02:00
co-authored by Claude Opus 5.5
parent 4dba794b26
commit 68c87bc3cd
11 changed files with 52 additions and 9 deletions
+37 -7
View File
@@ -1,4 +1,4 @@
"""Fake Radarr (19891), Sonarr (19892) and SABnzbd (19893) serving the made-up
"""Fake Radarr (19891), Sonarr (19892), SABnzbd (19893) and Trakt (19894) serving the made-up
library in catalog.py, for the README screenshots. Only the API calls arr-tui
makes are answered; posters come from posters/ (see posters.sh)."""
import datetime, json, os, sys, threading, urllib.parse
@@ -8,7 +8,7 @@ import catalog
HERE = os.path.dirname(os.path.abspath(__file__))
TODAY = datetime.date.today()
GB = 1 << 30
RADARR, SONARR, SAB = 19891, 19892, 19893
RADARR, SONARR, SAB, TRAKT = 19891, 19892, 19893, 19894
def day(n): return (TODAY + datetime.timedelta(days=n)).isoformat()
@@ -98,10 +98,10 @@ def radarr(path, q):
return next(x for x in movies() if x["id"] == int(path.rsplit("/", 1)[1]))
if path.endswith("/movie/lookup"):
term = q.get("term", [""])[0].lower()
if term.startswith("tmdb:"):
return [m for m in movies() + [UPCOMING] if m["tmdbId"] == int(term[5:])]
res = [m for m in movies() if term and term in m["title"].lower()]
extra = [{"id": 0, "title": "The Long Orbit: Return", "year": 2027, "overview": "The sequel.", "tmdbId": 9001, "titleSlug": "the-long-orbit-return",
"images": [{"coverType": "poster", "remoteUrl": "http://127.0.0.1:%d/remote/m1.jpg" % RADARR}], "status": "announced", "ratings": {}}]
return res + (extra if "orbit" in term else [])
return res + ([UPCOMING] if "orbit" in term else [])
if path.endswith("/qualityprofile"): return [{"id": 1, "name": "Any"}, {"id": 4, "name": "HD-1080p"}, {"id": 5, "name": "Ultra-HD"}]
if path.endswith("/rootfolder"): return [{"id": 1, "path": "/movies", "freeSpace": 3 << 40, "accessible": True}]
if path.endswith("/release"):
@@ -125,6 +125,11 @@ def radarr(path, q):
def sonarr(path, q):
if path.endswith("/system/status"): return {"appName": "Sonarr", "version": "4.0.15"}
if path.endswith("/series/lookup"):
term = q.get("term", [""])[0].lower()
if term.startswith("tvdb:"):
return [x for x in series() if x["tvdbId"] == int(term[5:])]
return [x for x in series() if term in x["title"].lower()]
if path.endswith("/series"): return series()
if "/series/" in path and path.rsplit("/", 1)[1].isdigit():
return next(x for x in series() if x["id"] == int(path.rsplit("/", 1)[1]))
@@ -148,6 +153,28 @@ def sonarr(path, q):
return out
return []
# A title Trakt lists that is not in the library yet.
UPCOMING = {"id": 0, "title": catalog.UPCOMING[1], "year": catalog.UPCOMING[2], "overview": "Years after the first voyage, a second crew follows the freighter's last signal.",
"tmdbId": 9001, "titleSlug": "the-long-orbit-return", "genres": ["Science Fiction"], "studio": "Halcyon Pictures", "runtime": 0,
"images": [{"coverType": "poster", "remoteUrl": "http://127.0.0.1:%d/remote/m9001.jpg" % RADARR}], "status": "announced", "ratings": {}}
def trakt(path):
def movie(id, title, year): return {"title": title, "year": year, "ids": {"trakt": id, "slug": "x", "tmdb": 1000 + id if id < 9000 else id}}
def show(id): s = next(x for x in catalog.SERIES if x[0] == id); return {"title": s[1], "year": s[2], "ids": {"trakt": id, "slug": "x", "tvdb": 2000 + id}}
by = {m[0]: m for m in catalog.MOVIES}
if path == "/movies/trending":
return [{"watchers": n, "movie": movie(i, by[i][1], by[i][2])} for i, n in [(13, 2841), (1, 2217), (11, 1604), (6, 1186), (3, 902), (8, 655), (16, 412), (12, 371)]]
if path == "/movies/anticipated":
return [{"list_count": 18342, "movie": movie(9001, "The Long Orbit: Return", 2027)}] + \
[{"list_count": n, "movie": movie(i, by[i][1], by[i][2])} for i, n in [(15, 9120), (9, 6204), (4, 3311)]]
if path.startswith("/movies/"):
return [{"watcher_count": n * 10, "movie": movie(i, by[i][1], by[i][2])} for i, n in [(1, 5120), (13, 4870), (3, 3010), (2, 1200)]]
if path == "/shows/trending":
return [{"watchers": n, "show": show(i)} for i, n in [(1, 3022), (5, 2410), (3, 1170), (2, 640)]]
if path.startswith("/shows/"):
return [{"watcher_count": n, "show": show(i)} for i, n in [(3, 51200), (1, 40100), (4, 22000)]]
return []
def sab(q):
mode = q.get("mode", [""])[0]
if mode == "version": return {"version": "4.5.1"}
@@ -184,11 +211,14 @@ def handler(port):
if not os.path.exists(p): self.send_response(404); self.end_headers(); return
data = open(p, "rb").read()
self.send_response(200); self.send_header("Content-Type", "image/jpeg"); self.send_header("Content-Length", str(len(data))); self.end_headers(); self.wfile.write(data); return
out = sab(q) if port == SAB else (radarr(path, q) if port == RADARR else sonarr(path, q))
if port == TRAKT:
out = trakt(path)
else:
out = sab(q) if port == SAB else (radarr(path, q) if port == RADARR else sonarr(path, q))
b = json.dumps(out).encode()
self.send_response(200); self.send_header("Content-Type", "application/json"); self.send_header("Content-Length", str(len(b))); self.end_headers(); self.wfile.write(b)
return H
for port in (RADARR, SONARR):
for port in (RADARR, SONARR, TRAKT):
threading.Thread(target=ThreadingHTTPServer(("127.0.0.1", port), handler(port)).serve_forever, daemon=True).start()
ThreadingHTTPServer(("127.0.0.1", SAB), handler(SAB)).serve_forever()