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, cutoff_utc = current_week() total_episodes = int(os.environ.get("TOTAL_EPISODES", 1155)) latest = get_latest_sunday() previous = get_previous_sunday() jellyfin.clear_debug_logs() try: ep_info = jellyfin.find_last_one_piece_before(cutoff_utc) except Exception as e: debug_output = jellyfin.get_debug_logs() return jsonify({"error": str(e), "debug": debug_output}), 502 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"), ) 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 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)