From b3e3a97c2bce21adcb2871bd6df6e40e26cd2f83 Mon Sep 17 00:00:00 2001 From: Pat Teruel Date: Sun, 22 Feb 2026 16:07:56 +0800 Subject: [PATCH] Enhancements --- app.py | 10 ++-- clients/jellyfin.py | 119 +++++++++++++++++++++++++------------------- 2 files changed, 76 insertions(+), 53 deletions(-) diff --git a/app.py b/app.py index 41ea299..1535d36 100644 --- a/app.py +++ b/app.py @@ -111,15 +111,19 @@ def index(): @app.route("/api/report") def report(): - week_start, week_end, cutoff_utc = current_week() + week_start, week_end, _ = current_week() # This is the week range for display (e.g., Feb 15-21) total_episodes = int(os.environ.get("TOTAL_EPISODES", 1155)) - latest = get_latest_sunday() + latest = get_latest_sunday() # This is the cutoff for searching (e.g., Feb 21 16:00 UTC) previous = get_previous_sunday() jellyfin.clear_debug_logs() + # Convert latest_sunday string back to datetime for cutoff + latest_sunday_dt = datetime.fromisoformat(latest.replace("Z", "+00:00")) + user_id = os.environ["JELLYFIN_USER_ID"] + try: - ep_info = jellyfin.find_last_one_piece_before(cutoff_utc) + ep_info = jellyfin.find_last_one_piece_episode(latest_sunday_dt, user_id) except Exception as e: debug_output = jellyfin.get_debug_logs() return jsonify({"error": str(e), "debug": debug_output}), 502 diff --git a/clients/jellyfin.py b/clients/jellyfin.py index 7e5ca5c..fd3882c 100644 --- a/clients/jellyfin.py +++ b/clients/jellyfin.py @@ -103,18 +103,23 @@ def extract_episode_number(name: str) -> int: return int(match.group(1)) -def find_last_one_piece_before(cutoff_utc: datetime) -> dict: - """Return info about the last One Piece episode watched before cutoff_utc. - +def find_last_one_piece_episode(cutoff_utc: datetime, user_id: str) -> dict: + """Find the last One Piece episode watched BEFORE cutoff_utc by the given user. + + Filters: + 1. Entry date must be strictly before cutoff_utc + 2. UserId must match + 3. Name must start with '{user} has finished playing One Piece' + 4. Type must be VideoPlaybackStopped + + Returns the most recent matching entry. Returns: {episode_number, item_id, watched_at (ISO str), media_name} """ - c = _cfg() min_date = cutoff_utc - timedelta(days=14) - cutoff_utc_str = cutoff_utc.isoformat() - _debug.add(f"Searching for One Piece episodes before {cutoff_utc_str}") - _debug.add(f"User ID: {c['user']}") - _debug.add(f"Search window: {min_date.isoformat()} to {cutoff_utc_str}") + _debug.add(f"Searching for One Piece episodes watched before: {cutoff_utc.isoformat()}") + _debug.add(f"User ID: {user_id}") + _debug.add(f"Search window: {min_date.isoformat()} to {cutoff_utc.isoformat()}") _debug.add("") try: @@ -124,47 +129,61 @@ def find_last_one_piece_before(cutoff_utc: datetime) -> dict: raise _debug.add("") - one_piece_count = 0 - for entry in entries: - entry_type = entry.get("Type") - is_playback = entry_type == "VideoPlaybackStopped" - user_id = entry.get("UserId") - is_user = user_id == c["user"] - name = entry.get("Name", "") - is_one_piece = "One Piece" in name - - if is_playback and is_user and is_one_piece: - one_piece_count += 1 - date_str = entry.get("Date", "") - try: - entry_dt = datetime.fromisoformat(date_str.replace("Z", "+00:00")) - is_before_cutoff = entry_dt < cutoff_utc - - if is_before_cutoff: - _debug.add(f"✓ Found One Piece entry: {name}") - _debug.add(f" Date: {date_str} (before cutoff)") - - item_id = entry.get("ItemId") - if item_id: - try: - source = get_playback_info(item_id) - ep_num = extract_episode_number(source["Name"]) - _debug.add(f" Extracted episode: {ep_num}") - _debug.add("") - return { - "episode_number": ep_num, - "item_id": item_id, - "watched_at": date_str, - "media_name": source["Name"], - } - except Exception as e: - _debug.add(f" ✗ Error extracting episode: {e}") - else: - _debug.add(f" No ItemId in entry") - except ValueError as ve: - _debug.add(f"✗ Failed to parse date {date_str}: {ve}") - _debug.add(f"✗ No One Piece episode found before cutoff") - _debug.add(f" Found {one_piece_count} One Piece entries total (some may be after cutoff)") - raise RuntimeError("No One Piece episode found before the Sunday cutoff in the last 14 days") + # Filter: Type == VideoPlaybackStopped, UserId matches, Date before cutoff, Name pattern + candidates = [] + for entry in entries: + if entry.get("Type") != "VideoPlaybackStopped": + continue + if entry.get("UserId") != user_id: + continue + if not entry.get("Name", "").startswith(f"{user_id.split('@')[0] if '@' in user_id else 'pat'} has finished playing One Piece"): + # Try simple pattern: contains "has finished playing One Piece" + if "has finished playing One Piece" not in entry.get("Name", ""): + continue + + date_str = entry.get("Date", "") + try: + entry_dt = datetime.fromisoformat(date_str.replace("Z", "+00:00")) + if entry_dt < cutoff_utc: + candidates.append((entry_dt, entry)) + except ValueError: + continue + + if not candidates: + _debug.add(f"✗ No matching One Piece episodes found before cutoff") + raise RuntimeError("No One Piece episode found before the Sunday cutoff") + + # Sort by date descending (newest first) and take the first + candidates.sort(key=lambda x: x[0], reverse=True) + latest_dt, latest_entry = candidates[0] + + name = latest_entry.get("Name", "") + date_str = latest_entry.get("Date", "") + item_id = latest_entry.get("ItemId") + + _debug.add(f"✓ Found {len(candidates)} matching One Piece entries") + _debug.add(f"✓ Latest: {name}") + _debug.add(f" Watched at: {date_str}") + _debug.add("") + + if not item_id: + _debug.add(f"✗ No ItemId in entry") + raise RuntimeError("Entry has no ItemId") + + try: + source = get_playback_info(item_id) + ep_num = extract_episode_number(source["Name"]) + _debug.add(f"✓ Extracted episode: {ep_num}") + _debug.add("") + + return { + "episode_number": ep_num, + "item_id": item_id, + "watched_at": date_str, + "media_name": source["Name"], + } + except Exception as e: + _debug.add(f"✗ Error extracting episode: {e}") + raise