Finalize fixes

This commit is contained in:
Pat Teruel committed 2026-02-22 17:45:11 +08:00
1 parent b3e3a97c2b
commit 1f216a8e8d
2 files changed
+120 -14

No files matched your search

+15 -5
View File
@@ -113,21 +113,33 @@ def index():
def report(): def report():
week_start, week_end, _ = current_week() # This is the week range for display (e.g., Feb 15-21) 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)) total_episodes = int(os.environ.get("TOTAL_EPISODES", 1155))
latest = get_latest_sunday() # This is the cutoff for searching (e.g., Feb 21 16:00 UTC) latest = get_latest_sunday() # e.g., 2026-02-21T16:00:00.000Z
previous = get_previous_sunday() previous = get_previous_sunday() # e.g., 2026-02-14T16:00:00.000Z
jellyfin.clear_debug_logs() jellyfin.clear_debug_logs()
# Convert latest_sunday string back to datetime for cutoff # Convert to datetime for cutoff
latest_sunday_dt = datetime.fromisoformat(latest.replace("Z", "+00:00")) latest_sunday_dt = datetime.fromisoformat(latest.replace("Z", "+00:00"))
previous_sunday_dt = datetime.fromisoformat(previous.replace("Z", "+00:00"))
user_id = os.environ["JELLYFIN_USER_ID"] user_id = os.environ["JELLYFIN_USER_ID"]
# Fetch latest episode (watched before latest Sunday)
try: try:
ep_info = jellyfin.find_last_one_piece_episode(latest_sunday_dt, user_id) ep_info = jellyfin.find_last_one_piece_episode(latest_sunday_dt, user_id)
except Exception as e: except Exception as e:
debug_output = jellyfin.get_debug_logs() debug_output = jellyfin.get_debug_logs()
return jsonify({"error": str(e), "debug": debug_output}), 502 return jsonify({"error": str(e), "debug": debug_output}), 502
# Fetch previous week episode (watched before previous Sunday)
try:
prev_ep_info = jellyfin.find_last_one_piece_before_previous_sunday(previous_sunday_dt, user_id)
previous_episode = prev_ep_info["episode_number"]
except Exception as e:
_debug.add(f"✗ Could not find previous Sunday episode: {e}")
_debug.add("✓ Falling back to DB seed or latest episode")
prev = db.get_previous_episode(week_start.isoformat())
previous_episode = prev["episode_number"] if prev else ep_info["episode_number"]
db.save_episode( db.save_episode(
episode_number=ep_info["episode_number"], episode_number=ep_info["episode_number"],
week_start=week_start.isoformat(), week_start=week_start.isoformat(),
@@ -137,8 +149,6 @@ def report():
watched_at=ep_info.get("watched_at"), watched_at=ep_info.get("watched_at"),
) )
prev = db.get_previous_episode(week_start.isoformat())
previous_episode = prev["episode_number"] if prev else ep_info["episode_number"]
watched_this_week = ep_info["episode_number"] - previous_episode watched_this_week = ep_info["episode_number"] - previous_episode
date_range = format_date_range(week_start, week_end) date_range = format_date_range(week_start, week_end)
+105 -9
View File
@@ -38,16 +38,22 @@ def clear_debug_logs():
_debug.logs = [] _debug.logs = []
def get_activity_logs(min_date: datetime) -> list: def get_activity_logs(min_date: datetime = None, limit: int = 200) -> list:
"""Return activity log entries since min_date (UTC datetime).""" """Return activity log entries. Optionally filtered by min_date.
Args:
min_date: Optional minimum date filter (UTC datetime)
limit: Number of entries to fetch (default 200)
"""
c = _cfg() c = _cfg()
url = f"{c['host']}/System/ActivityLog/Entries" url = f"{c['host']}/System/ActivityLog/Entries"
params = { params = {
"api_key": c["key"], "api_key": c["key"],
"hasUserId": "true", "hasUserId": "true",
"limit": "200", "limit": str(limit),
"minDate": min_date.strftime("%Y-%m-%dT%H:%M:%S.000000Z"),
} }
if min_date:
params["minDate"] = min_date.strftime("%Y-%m-%dT%H:%M:%S.000000Z")
curl_cmd = f"curl --location '{url}?" curl_cmd = f"curl --location '{url}?"
curl_params = [] curl_params = []
@@ -57,7 +63,7 @@ def get_activity_logs(min_date: datetime) -> list:
curl_cmd += "' \\\n --header 'accept: application/json'" curl_cmd += "' \\\n --header 'accept: application/json'"
_debug.add(f"GET {url}") _debug.add(f"GET {url}")
_debug.add(f" params: hasUserId=true, limit=200, minDate={params['minDate']}") _debug.add(f" params: hasUserId=true, limit={limit}" + (f", minDate={params['minDate']}" if min_date else ""))
_debug.add("") _debug.add("")
_debug.add("Curl command:") _debug.add("Curl command:")
_debug.add(curl_cmd) _debug.add(curl_cmd)
@@ -74,6 +80,7 @@ def get_activity_logs(min_date: datetime) -> list:
raise raise
def get_playback_info(item_id: str) -> dict: def get_playback_info(item_id: str) -> dict:
"""Return the first MediaSource dict for the given item.""" """Return the first MediaSource dict for the given item."""
c = _cfg() c = _cfg()
@@ -103,13 +110,104 @@ def extract_episode_number(name: str) -> int:
return int(match.group(1)) return int(match.group(1))
def _find_last_one_piece_in_entries(entries: list, cutoff_utc: datetime, user_id: str) -> dict:
"""Extract the last One Piece episode from a list of entries, watched before cutoff_utc.
Returns {episode_number, item_id, watched_at, media_name} or raises RuntimeError if not found.
"""
candidates = []
for entry in entries:
if entry.get("Type") != "VideoPlaybackStopped":
continue
if entry.get("UserId") != user_id:
continue
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:
raise RuntimeError("No matching One Piece episodes found")
# Sort by date descending (newest 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")
if not item_id:
raise RuntimeError("Entry has no ItemId")
source = get_playback_info(item_id)
ep_num = extract_episode_number(source["Name"])
return {
"episode_number": ep_num,
"item_id": item_id,
"watched_at": date_str,
"media_name": source["Name"],
"name": name,
}
def find_last_one_piece_before_previous_sunday(previous_sunday_utc: datetime, user_id: str) -> dict:
"""Find the latest One Piece episode watched BEFORE the previous Sunday.
Uses exponential backoff: starts with limit=500, doubles until found or limit >= 32000.
Returns: {episode_number, item_id, watched_at, media_name}
"""
_debug.add(f"Searching for episode watched before previous Sunday: {previous_sunday_utc.isoformat()}")
_debug.add(f"Using exponential backoff: 500 → 1000 → 2000 → 4000 → 8000 → 16000 → 32000")
_debug.add("")
limit = 500
max_limit = 32000
attempt = 0
while limit <= max_limit:
attempt += 1
_debug.add(f"Attempt {attempt}: Fetching {limit} activity log entries...")
try:
entries = get_activity_logs(limit=limit)
except Exception as e:
_debug.add(f"✗ Failed to fetch: {e}")
raise
_debug.add(f"✓ Got {len(entries)} entries, searching...")
_debug.add("")
try:
result = _find_last_one_piece_in_entries(entries, previous_sunday_utc, user_id)
_debug.add(f"✓ Found: {result['name']}")
_debug.add(f" Watched at: {result['watched_at']}")
_debug.add(f" Extracted episode: {result['episode_number']}")
_debug.add("")
return result
except RuntimeError as e:
_debug.add(f"✗ {e} (trying next limit...)")
_debug.add("")
limit *= 2
_debug.add(f"✗ Exceeded max limit ({max_limit}) without finding episode")
raise RuntimeError(f"Could not find previous Sunday episode after searching up to {max_limit} entries")
def find_last_one_piece_episode(cutoff_utc: datetime, user_id: str) -> dict: 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. """Find the last One Piece episode watched BEFORE cutoff_utc by the given user.
Filters: Filters:
1. Entry date must be strictly before cutoff_utc 1. Entry date must be strictly before cutoff_utc
2. UserId must match 2. UserId must match
3. Name must start with '{user} has finished playing One Piece' 3. Name must contain 'has finished playing One Piece'
4. Type must be VideoPlaybackStopped 4. Type must be VideoPlaybackStopped
Returns the most recent matching entry. Returns the most recent matching entry.
@@ -123,7 +221,7 @@ def find_last_one_piece_episode(cutoff_utc: datetime, user_id: str) -> dict:
_debug.add("") _debug.add("")
try: try:
entries = get_activity_logs(min_date) entries = get_activity_logs(limit=200, min_date=min_date)
except Exception as e: except Exception as e:
_debug.add(f"✗ Failed to fetch activity logs: {e}") _debug.add(f"✗ Failed to fetch activity logs: {e}")
raise raise
@@ -137,8 +235,6 @@ def find_last_one_piece_episode(cutoff_utc: datetime, user_id: str) -> dict:
continue continue
if entry.get("UserId") != user_id: if entry.get("UserId") != user_id:
continue 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", ""): if "has finished playing One Piece" not in entry.get("Name", ""):
continue continue