diff --git a/app.py b/app.py index 37ede9d..4ab59b1 100644 --- a/app.py +++ b/app.py @@ -726,6 +726,61 @@ def ombi_get(endpoint): return resp.json() +def _tmdb_poster(path): + """Ombi's movie search returns a bare TMDB path ('/abc123.jpg'), not a + usable URL; TV search returns a full TVDB banner URL already.""" + if not path: + return "" + if path.startswith("http"): + return path + return f"https://image.tmdb.org/t/p/w300{path}" + + +def ombi_search(q, limit=12): + """Movies and TV matching q, via Ombi's own search — it already sits in + front of TheMovieDB/TVDB, so this is one call instead of two more API + keys. Cross-referenced against the cached Sonarr/Radarr title sets so a + title already held shows that instead of an Add button. + """ + term = q.strip() + if not term: + return {"status": "ok", "results": []} + encoded = requests.utils.quote(term) + try: + radarr_titles = {t.lower() for t in get_radarr_summary().get("radarr_titles", [])} + sonarr_titles = {t.lower() for t in get_sonarr_summary().get("sonarr_titles", [])} + results = [] + try: + for m in ombi_get(f"Search/movie/{encoded}")[:limit]: + title = m.get("title", "") + results.append({ + "title": title, + "year": (m.get("releaseDate") or "")[:4], + "poster": _tmdb_poster(m.get("posterPath")), + "kind": "Film", + "target": "radarr", + "held": bool(m.get("available")) or title.lower() in radarr_titles, + }) + except Exception: + pass + try: + for s in ombi_get(f"Search/tv/{encoded}")[:limit]: + title = s.get("title", "") + results.append({ + "title": title, + "year": (s.get("firstAired") or "")[:4], + "poster": s.get("banner") or _tmdb_poster(s.get("posterPath")), + "kind": "TV", + "target": "sonarr", + "held": bool(s.get("available")) or title.lower() in sonarr_titles, + }) + except Exception: + pass + return {"status": "ok", "results": results} + except Exception as e: + return {"status": "error", "error": str(e), "results": []} + + def _ombi_status_label(available, approved, denied): if denied: return "Denied" @@ -1404,6 +1459,41 @@ def get_recently_added(limit=6): return items +def search_library(q, limit=24): + """Titles in Sonarr/Radarr matching q, case-insensitive substring. + + Filters the same cached lists get_recently_added uses — no new fetch. + """ + needle = q.strip().lower() + if not needle: + return [] + items = [] + try: + for sr in sonarr_series_cached(): + if needle in sr.get("title", "").lower(): + items.append({ + "title": sr.get("title", "Unknown"), + "kind": "TV", + "year": str(sr.get("year", "")), + "poster": _poster_url(sr.get("images")), + }) + except Exception: + pass + try: + for mv in radarr_movies_cached(): + if needle in mv.get("title", "").lower(): + items.append({ + "title": mv.get("title", "Unknown"), + "kind": "Film", + "year": str(mv.get("year", "")), + "poster": _poster_url(mv.get("images")), + }) + except Exception: + pass + items.sort(key=lambda x: x["title"].lower()) + return items[:limit] + + def get_activity_feed(sabnzbd, ombi, limit=25): """A single chronological feed assembled from what the fetchers return. @@ -1508,9 +1598,11 @@ def watch_page(): @login_required def library_page(): r = fetch_services(["sonarr", "radarr"]) + q = request.args.get("q", "").strip() return render_template("library.html", page="library", sonarr=r["sonarr"], radarr=r["radarr"], - recently_added=get_recently_added(12)) + recently_added=get_recently_added(12), + q=q, search_results=search_library(q) if q else None) @app.route("/discover") @@ -1519,9 +1611,11 @@ def discover_page(): r = fetch_services(["sonarr", "radarr", "rt_movies", "rt_tv"]) sonarr_titles = set(r["sonarr"].get("sonarr_titles", [])) radarr_titles = set(r["radarr"].get("radarr_titles", [])) + q = request.args.get("q", "").strip() return render_template("discover.html", page="discover", rt_movies=_filter_rt(r["rt_movies"], radarr_titles), - rt_tv=_filter_rt(r["rt_tv"], sonarr_titles)) + rt_tv=_filter_rt(r["rt_tv"], sonarr_titles), + q=q, search=ombi_search(q) if q else None) @app.route("/requests") @@ -1760,7 +1854,7 @@ def api_delete_show(): @login_required def settings_page(): s = load_settings() - return render_template("settings.html", + return render_template("settings.html", page="settings", timezone=s.get("timezone", "America/New_York"), sonarr_api_key=SONARR_API_KEY, radarr_api_key=RADARR_API_KEY, diff --git a/static/css/teletext.css b/static/css/teletext.css index ad36959..69eb404 100644 --- a/static/css/teletext.css +++ b/static/css/teletext.css @@ -465,10 +465,15 @@ a.tt-row:focus-visible .tt-row-sub { color: var(--tt-black); } .tt-empty { padding: 0 1ch; color: var(--ink-dim); } /* ── Fastext ──────────────────────────────────────────── - The four coloured keys along the bottom of every teletext - page. Real navigation, not decoration: click one, or press - its initial. Deliberately NOT fixed to the viewport — it is - the last row of the page, where it always was. */ + The four coloured keys. Real navigation, not decoration: + click one, or press its initial. Deliberately NOT fixed to + the viewport — it is a row of the page, where it always + was, not furniture bolted over it. On the index it closes + the page, under Now Showing/Just Added, because the index's + own body is already the full nav. Everywhere else — where + these four keys are the only nav on the page — it opens the + page, right under the header, so it doesn't need a scroll + to reach. */ .fastext { display: grid; grid-template-columns: repeat(4, 1fr); @@ -477,6 +482,9 @@ a.tt-row:focus-visible .tt-row-sub { color: var(--tt-black); } padding: 0 var(--gutter); gap: 1ch; } +.fastext-top { + margin: var(--row) auto calc(var(--row) * 1.5); +} .fastext a { display: flex; align-items: center; diff --git a/static/js/service.js b/static/js/service.js index d95f0b2..f7e71fc 100644 --- a/static/js/service.js +++ b/static/js/service.js @@ -129,7 +129,7 @@ }) .then(function (r) { return r.json(); }) .then(function (d) { - if (d && d.ok) { + if (d && d.success) { btn.classList.add('done'); btn.textContent = 'ADDED'; } else { diff --git a/templates/_shell.html b/templates/_shell.html index 6970434..2509aa4 100644 --- a/templates/_shell.html +++ b/templates/_shell.html @@ -45,19 +45,28 @@ provenance. -
- {% block content %}{% endblock %} -
- {#- Fastext: the four coloured keys. Each page names its own four, so the - keys always point somewhere useful from where you are. -#} -