diff --git a/CLAUDE.md b/CLAUDE.md index 5e06912..10ca8d5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -202,13 +202,6 @@ See the `deploy-arr-summary` skill for the Forgejo push-to-deploy pipeline, manual container restart steps, deploy verification, and the Nginx Proxy Manager / TLS setup and its Cox-port-80/DNS-01 constraint. -**Every commit should be matched with a version change.** Bump -`APP_VERSION` / `APP_VERSION_DATE` near the top of `app.py` as part of -the same commit. This is what makes the footer (`v · -`) a trustworthy signal that a given deploy actually -landed — a commit without a version bump can't be distinguished from -"not deployed yet" by looking at the live site. - ## Logins See the `manage-accounts` skill for the multi-user vs. legacy diff --git a/app.py b/app.py index b8e3977..69f014c 100644 --- a/app.py +++ b/app.py @@ -22,8 +22,8 @@ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # Docker tab caches stale container IDs, so the footer is the reliable way # to confirm a `docker compose up -d --build` actually took effect. # Both are env-overridable if the deploy pipeline ever injects them. -APP_VERSION = os.environ.get("APP_VERSION", "2.1.0") -APP_VERSION_DATE = os.environ.get("APP_VERSION_DATE", "2026-08-31") +APP_VERSION = os.environ.get("APP_VERSION", "2.0.0") +APP_VERSION_DATE = os.environ.get("APP_VERSION_DATE", "2026-08-29") # Must point at a path that survives `docker compose up --build` — the # container filesystem does not. See the data volume in docker-compose.yml. @@ -612,7 +612,6 @@ def get_tautulli_summary(): top_movie_list = cached["top_movie_list"] top_played = cached["top_played"] top_users = cached["top_users"] - active_users = cached["active_users"] else: # Top 3 most watched TV shows (30d) tv_data = tautulli_get("get_home_stats", "&stat_id=top_tv&stats_count=3&stats_type=plays&time_range=30") @@ -656,36 +655,9 @@ def get_tautulli_summary(): if len(top_users) >= 3: break - # Active viewers (10d) — everyone with a play in the window, for - # the Watch page's "click a name for their 48h history" list. - # A dedicated call rather than reusing top_played/top_users - # above: those are capped at 3 and tuned for different windows - # (all-time-ish top-3, 30d top-10), neither of which is "who's - # been around lately". - cutoff_date = (datetime.now() - timedelta(days=10)).strftime("%Y-%m-%d") - active_data = tautulli_get("get_history", f"&length=1000&order_column=date&order_dir=desc&after={cutoff_date}") - active_slots = [] - if isinstance(active_data, dict): - active_slots = active_data.get("data", []) - if isinstance(active_slots, dict): - active_slots = active_slots.get("data", []) - active_by_user = {} - for entry in active_slots: - username = entry.get("friendly_name") or entry.get("user", "Unknown") - if username.lower() == "ninja_hippo": - continue - rec = active_by_user.setdefault(username, {"username": username, "plays": 0, "last_watched_ts": 0}) - rec["plays"] += 1 - rec["last_watched_ts"] = max(rec["last_watched_ts"], entry.get("date", 0) or 0) - active_users = sorted(active_by_user.values(), key=lambda x: x["last_watched_ts"], reverse=True) - for u in active_users: - u["last_watched"] = (datetime.fromtimestamp(u["last_watched_ts"]).strftime("%d %b · %H:%M") - if u["last_watched_ts"] else "") - _tautulli_stats_cache = { "data": {"top_tv_list": top_tv_list, "top_movie_list": top_movie_list, - "top_played": top_played, "top_users": top_users, - "active_users": active_users}, + "top_played": top_played, "top_users": top_users}, "expires": now + TAUTULLI_CACHE_TTL, } @@ -736,7 +708,6 @@ def get_tautulli_summary(): "top_movie_list": top_movie_list, "top_played": top_played, "top_users": top_users, - "active_users": active_users, "active_streams": stream_count, "sessions": sessions, } @@ -744,41 +715,6 @@ def get_tautulli_summary(): return {"status": "error", "error": str(e)} -def get_user_watch_history(username, hours=48): - """One viewer's plays in the last `hours`, for the Watch page's - per-user drill-down. Fetched on demand, not cached — this is a click - target, not something every page load needs to pay for.""" - try: - cutoff_ts = time.time() - hours * 3600 - encoded = requests.utils.quote(username) - hist = tautulli_get("get_history", f"&user={encoded}&length=200&order_column=date&order_dir=desc") - slots = [] - if isinstance(hist, dict): - slots = hist.get("data", []) - if isinstance(slots, dict): - slots = slots.get("data", []) - items = [] - for entry in slots: - ts = entry.get("date", 0) or 0 - if ts < cutoff_ts: - continue - try: - percent = int(float(entry.get("percent_complete", 0) or 0)) - except (TypeError, ValueError): - percent = 0 - items.append({ - "title": entry.get("full_title") or entry.get("title", "Unknown"), - "media_type": entry.get("media_type", ""), - "percent": max(0, min(100, percent)), - "when": datetime.fromtimestamp(ts).strftime("%d %b · %H:%M") if ts else "", - }) - # Note: "items" collides with dict.items in Jinja attribute lookup - # (getattr wins over __getitem__), so the template key is "entries". - return {"status": "ok", "entries": items} - except Exception as e: - return {"status": "error", "error": str(e)} - - # --------------------------------------------------------------------------- # Ombi # --------------------------------------------------------------------------- @@ -1680,10 +1616,7 @@ def home(): @login_required def watch_page(): r = fetch_services(["tautulli"]) - selected_user = request.args.get("user", "").strip() - user_history = get_user_watch_history(selected_user) if selected_user else None - return render_template("watch.html", page="watch", tautulli=r["tautulli"], - selected_user=selected_user, user_history=user_history) + return render_template("watch.html", page="watch", tautulli=r["tautulli"]) @app.route("/library") diff --git a/templates/watch.html b/templates/watch.html index 335cec6..3d0b63c 100644 --- a/templates/watch.html +++ b/templates/watch.html @@ -11,29 +11,6 @@ {% else %} {%- set sessions = tautulli.sessions or [] -%} - {% if selected_user %} -
History — {{ selected_user }}last 48 hours
- {% if user_history.status != 'ok' %} - {{ service_error('Tautulli', user_history.error) }} - {% elif user_history.entries %} -
- {% for it in user_history.entries %} -
- {{ (it.media_type or 'play') | upper }} - - {{ it.title }} - {{ it.when }} - - {{ it.percent }}% -
- {% endfor %} -
- {% else %} -

Nothing played in the last 48 hours.

- {% endif %} -

← All viewers

- {% endif %} -
Streaming Now {{ tautulli.active_streams }} active @@ -98,23 +75,5 @@ {% endfor %}
{% endif %} - -
Active Viewerslast 10 days
- {% if tautulli.active_users %} - - {% else %} -

No viewers in the last 10 days.

- {% endif %} {% endif %} {% endblock %}