190 lines
6.6 KiB
Python
190 lines
6.6 KiB
Python
from flask import Flask, render_template, jsonify
|
|
from datetime import datetime, timezone, timedelta
|
|
import os
|
|
from dotenv import load_dotenv
|
|
|
|
load_dotenv()
|
|
|
|
import db
|
|
from clients import jellyfin
|
|
|
|
app = Flask(__name__)
|
|
|
|
PHT = timezone(timedelta(hours=8))
|
|
|
|
|
|
def _sunday_midnight_pht_for(now_pht=None):
|
|
"""Return the Sunday midnight (start of day) in PHT used for week-start logic.
|
|
|
|
This function treats Sunday as belonging to the previous week (used for producing
|
|
week ranges like "Feb 15-21" when today is Sunday Feb 22).
|
|
"""
|
|
now_pht = now_pht or datetime.now(PHT)
|
|
wd = now_pht.weekday()
|
|
days_since_sunday = (wd + 1) % 7
|
|
if wd == 6:
|
|
days_since_sunday = 7
|
|
sunday_midnight_pht = (now_pht - timedelta(days=days_since_sunday)).replace(
|
|
hour=0, minute=0, second=0, microsecond=0
|
|
)
|
|
return sunday_midnight_pht
|
|
|
|
|
|
def _latest_sunday_midnight_pht(now_pht=None):
|
|
"""Return the latest Sunday midnight (start of day) in PHT including today if it's Sunday.
|
|
|
|
This is for computing the exact UTC cutoff timestamps for a given latest Sunday.
|
|
"""
|
|
now_pht = now_pht or datetime.now(PHT)
|
|
wd = now_pht.weekday()
|
|
days_since_sunday = (wd + 1) % 7 # Sunday -> 0
|
|
sunday_midnight_pht = (now_pht - timedelta(days=days_since_sunday)).replace(
|
|
hour=0, minute=0, second=0, microsecond=0
|
|
)
|
|
return sunday_midnight_pht
|
|
|
|
|
|
def current_week():
|
|
"""Return (week_start, week_end, cutoff_utc) for the current week in PHT.
|
|
|
|
week_start = most recent Sunday (PHT date)
|
|
week_end = that Sunday + 6 days (Saturday)
|
|
cutoff_utc = Sunday 00:00 PHT expressed in UTC (= Saturday 16:00 UTC)
|
|
"""
|
|
sunday_midnight_pht = _sunday_midnight_pht_for()
|
|
week_start = sunday_midnight_pht.date()
|
|
week_end = week_start + timedelta(days=6)
|
|
cutoff_utc = sunday_midnight_pht.astimezone(timezone.utc)
|
|
return week_start, week_end, cutoff_utc
|
|
|
|
|
|
def _iso_z(dt):
|
|
"""Format timezone-aware datetime to 'YYYY-MM-DDTHH:MM:SS.mmmZ' in UTC."""
|
|
s = dt.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")
|
|
return s[:-3] + "Z"
|
|
|
|
|
|
def get_latest_sunday():
|
|
"""Return latest Sunday start-of-day PHT expressed in UTC ISO Z format (inclusive)."""
|
|
sunday_midnight_pht = _latest_sunday_midnight_pht()
|
|
cutoff_utc = sunday_midnight_pht.astimezone(timezone.utc)
|
|
return _iso_z(cutoff_utc)
|
|
|
|
|
|
def get_previous_sunday():
|
|
"""Return the Sunday prior to the latest Sunday (UTC ISO Z)."""
|
|
sunday_midnight_pht = _latest_sunday_midnight_pht()
|
|
prev_midnight = sunday_midnight_pht - timedelta(weeks=1)
|
|
prev_cutoff_utc = prev_midnight.astimezone(timezone.utc)
|
|
return _iso_z(prev_cutoff_utc)
|
|
|
|
|
|
def format_date_range(week_start, week_end) -> str:
|
|
"""Format date range: 'Feb 8-14' (same month) or 'Mar 29 - Apr 4' (different)."""
|
|
if week_start.month == week_end.month:
|
|
return f"{week_start.strftime('%b %-d')}-{week_end.strftime('%-d')}"
|
|
return f"{week_start.strftime('%b %-d')} - {week_end.strftime('%b %-d')}"
|
|
|
|
|
|
@app.route("/")
|
|
def index():
|
|
week_start, week_end, _ = current_week()
|
|
config = {
|
|
"JELLYFIN_HOST": os.environ.get("JELLYFIN_HOST", ""),
|
|
"JELLYFIN_API_KEY": os.environ.get("JELLYFIN_API_KEY", ""),
|
|
"JELLYFIN_USER_ID": os.environ.get("JELLYFIN_USER_ID", ""),
|
|
"TOTAL_EPISODES": os.environ.get("TOTAL_EPISODES", "1155"),
|
|
"DB_PATH": os.environ.get("DB_PATH", "/app/data/episodes.db"),
|
|
}
|
|
latest = get_latest_sunday()
|
|
previous = get_previous_sunday()
|
|
return render_template(
|
|
"index.html",
|
|
week_start=week_start.isoformat(),
|
|
week_end=week_end.isoformat(),
|
|
date_range=format_date_range(week_start, week_end),
|
|
config=config,
|
|
latest_sunday=latest,
|
|
previous_sunday=previous,
|
|
)
|
|
|
|
|
|
@app.route("/api/report")
|
|
def report():
|
|
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() # e.g., 2026-02-21T16:00:00.000Z
|
|
previous = get_previous_sunday() # e.g., 2026-02-14T16:00:00.000Z
|
|
|
|
jellyfin.clear_debug_logs()
|
|
|
|
# Convert to datetime for cutoff
|
|
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"]
|
|
|
|
# Fetch latest episode (watched before latest Sunday)
|
|
try:
|
|
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
|
|
|
|
# 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(
|
|
episode_number=ep_info["episode_number"],
|
|
week_start=week_start.isoformat(),
|
|
week_end=week_end.isoformat(),
|
|
item_id=ep_info.get("item_id"),
|
|
media_name=ep_info.get("media_name"),
|
|
watched_at=ep_info.get("watched_at"),
|
|
)
|
|
|
|
watched_this_week = ep_info["episode_number"] - previous_episode
|
|
|
|
date_range = format_date_range(week_start, week_end)
|
|
report_text = (
|
|
f"ONE PIECE PROGRESS!!! ({date_range})\n"
|
|
f"Episode {ep_info['episode_number']}/{total_episodes}\n"
|
|
f"Around {watched_this_week} episodes watched this week!"
|
|
)
|
|
|
|
debug_output = jellyfin.get_debug_logs()
|
|
|
|
return jsonify({
|
|
"week_start": week_start.isoformat(),
|
|
"week_end": week_end.isoformat(),
|
|
"date_range": date_range,
|
|
"latest_episode": ep_info["episode_number"],
|
|
"previous_episode": previous_episode,
|
|
"watched_this_week": watched_this_week,
|
|
"total_episodes": total_episodes,
|
|
"media_name": ep_info.get("media_name"),
|
|
"watched_at": ep_info.get("watched_at"),
|
|
"report": report_text,
|
|
"latest_sunday": latest,
|
|
"previous_sunday": previous,
|
|
"debug": debug_output,
|
|
})
|
|
|
|
|
|
@app.route("/api/history")
|
|
def history():
|
|
return jsonify(db.get_all_history())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
db.init_db()
|
|
port = int(os.environ.get("PORT", 5000))
|
|
app.run(host="0.0.0.0", port=port, debug=False)
|
|
|