import json import os import logging import threading import webbrowser from pathlib import Path from typing import Any, Dict from flask import Flask, jsonify, make_response, render_template, request, send_file from services.run_bot import AutomationController APP_DIR = Path(__file__).resolve().parent ROOT_DIR = APP_DIR.parent CONFIG_PATH = APP_DIR / "config.json" logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s") app = Flask( __name__, template_folder=str(APP_DIR / "templates"), static_folder=str(APP_DIR / "static"), static_url_path="/static", ) controller = AutomationController() def load_saved_settings() -> Dict[str, Any]: default_settings = { "username": "", "password": "", "studio": "", "min_price": 6.0, } if not CONFIG_PATH.exists(): return default_settings try: data = json.loads(CONFIG_PATH.read_text(encoding="utf-8")) return { "username": data.get("username", default_settings["username"]), "password": data.get("password", default_settings["password"]), "studio": data.get("studio", default_settings["studio"]), "min_price": data.get("min_price", default_settings["min_price"]), } except Exception: return default_settings def save_settings(payload: Dict[str, Any]) -> None: data = { "username": payload.get("username", ""), "password": payload.get("password", ""), "studio": payload.get("studio", ""), "min_price": payload.get("min_price", 6.0), } CONFIG_PATH.write_text(json.dumps(data, indent=2), encoding="utf-8") @app.get("/") def index(): return render_template("index.html", settings=load_saved_settings()) @app.post("/start") def start(): payload = request.get_json(silent=True) or {} username = str(payload.get("username", "")).strip() password = str(payload.get("password", "")) studio = str(payload.get("studio", "")).strip() min_price = payload.get("min_price", "") if not username or not password or not studio: return jsonify({"ok": False, "error": "Username, password, and studio are required"}), 400 try: min_price_value = float(min_price) except Exception: return jsonify({"ok": False, "error": "Minimum price must be numeric"}), 400 result = controller.start(username=username, password=password, studio=studio, min_price=min_price_value) status_code = 200 if result.get("ok") else 400 return jsonify(result), status_code @app.post("/stop") def stop(): return jsonify(controller.stop()) @app.get("/status") def status(): return jsonify(controller.snapshot()) @app.get("/screenshot") def screenshot_route(): # Use absolute path to logs directory (project root) logs_dir = ROOT_DIR / "logs" latest = logs_dir / "screenshot_latest.png" # normalize latest = latest.resolve() logging.info(f"Screenshot request: ROOT_DIR={ROOT_DIR}, logs_dir={logs_dir}, latest={latest}, exists={latest.exists()}") if not latest.exists(): # List what's in the logs directory for debugging if logs_dir.exists(): files = list(logs_dir.glob("*.png")) logging.warning(f"Screenshot not found at {latest}. Files in {logs_dir}: {files}") else: logging.warning(f"Logs directory doesn't exist: {logs_dir}") return ("", 204) try: logging.info(f"Sending screenshot: {latest}") response = make_response(send_file(str(latest), mimetype="image/png")) response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0" response.headers["Pragma"] = "no-cache" response.headers["Expires"] = "0" return response except Exception as e: logging.error(f"Screenshot send failed: {e}") return ("", 500) @app.get("/download") def download(): snapshot = controller.snapshot() csv_path_raw = snapshot.get("last_csv_path") or str(ROOT_DIR / "exports" / "latest.csv") path = Path(csv_path_raw) # Normalize relative paths after project restructuring. if not path.is_absolute(): candidates = [ ROOT_DIR / path, APP_DIR / path, Path.cwd() / path, ] resolved = next((p for p in candidates if p.exists()), None) path = resolved or (ROOT_DIR / "exports" / "latest.csv") path = path.resolve() if not path.exists() or not path.is_file(): return jsonify({"ok": False, "error": "No CSV file is available yet"}), 404 try: return send_file(str(path), as_attachment=True, download_name=path.name, mimetype="text/csv") except Exception as exc: logging.exception("Download failed") return jsonify({"ok": False, "error": f"Download failed: {exc}"}), 500 @app.post("/save-settings") def save_settings_route(): payload = request.get_json(silent=True) or {} username = str(payload.get("username", "")).strip() password = str(payload.get("password", "")) studio = str(payload.get("studio", "")).strip() min_price = payload.get("min_price", 6.0) try: min_price_value = float(min_price) except Exception: return jsonify({"ok": False, "error": "Minimum price must be numeric"}), 400 save_settings({ "username": username, "password": password, "studio": studio, "min_price": min_price_value, }) return jsonify({"ok": True, "message": "Settings saved"}) if __name__ == "__main__": port = int(os.environ.get("PORT", "5000")) host = "0.0.0.0" if "PORT" in os.environ else "127.0.0.1" if "PORT" not in os.environ: threading.Timer(1.0, lambda: webbrowser.open_new(f"http://localhost:{port}")).start() app.run(host=host, port=port, debug=False, threaded=True)