Spaces:
Paused
Paused
| from __future__ import annotations | |
| import json | |
| import os | |
| import secrets | |
| import shutil | |
| from dataclasses import dataclass | |
| from pathlib import Path | |
| from cryptography.fernet import Fernet | |
| DEFAULT_PARALLEL_LIMIT = 2 | |
| POLL_INTERVAL_SECONDS = 10 | |
| LOGIN_RETRY_LIMIT = 6 | |
| TASK_BACKOFF_SECONDS = 6 | |
| BROWSER_PAGE_TIMEOUT = 40 | |
| LOGS_PAGE_SIZE = 180 | |
| SELENIUM_ERROR_LIMIT = 5 | |
| SELENIUM_RESTART_LIMIT = 5 | |
| SUBMIT_CAPTCHA_RETRY_LIMIT = 5 | |
| def _pick_binary(env_name: str, *fallbacks: str) -> str: | |
| env_value = os.getenv(env_name) | |
| if env_value: | |
| return env_value | |
| for candidate in fallbacks: | |
| if not candidate: | |
| continue | |
| if Path(candidate).exists(): | |
| return candidate | |
| resolved = shutil.which(candidate) | |
| if resolved: | |
| return resolved | |
| return fallbacks[0] if fallbacks else "" | |
| def _load_or_create_internal_secrets(data_dir: Path) -> tuple[str, str]: | |
| secret_file = data_dir / ".app_secrets.json" | |
| payload: dict[str, str] = {} | |
| if secret_file.exists(): | |
| try: | |
| raw_payload = json.loads(secret_file.read_text(encoding="utf-8")) | |
| if isinstance(raw_payload, dict): | |
| payload = { | |
| str(key): str(value) | |
| for key, value in raw_payload.items() | |
| if isinstance(value, str) | |
| } | |
| except (OSError, json.JSONDecodeError): | |
| payload = {} | |
| session_secret = payload.get("session_secret", "").strip() | |
| encryption_key = payload.get("encryption_key", "").strip() | |
| changed = False | |
| if not session_secret: | |
| session_secret = secrets.token_hex(32) | |
| changed = True | |
| if not encryption_key: | |
| encryption_key = Fernet.generate_key().decode("utf-8") | |
| changed = True | |
| if changed or not secret_file.exists(): | |
| secret_file.write_text( | |
| json.dumps( | |
| { | |
| "session_secret": session_secret, | |
| "encryption_key": encryption_key, | |
| }, | |
| ensure_ascii=False, | |
| indent=2, | |
| ), | |
| encoding="utf-8", | |
| ) | |
| return session_secret, encryption_key | |
| class AppConfig: | |
| root_dir: Path | |
| data_dir: Path | |
| db_path: Path | |
| session_secret: str | |
| encryption_key: str | |
| super_admin_username: str | |
| super_admin_password: str | |
| default_parallel_limit: int | |
| poll_interval_seconds: int | |
| login_retry_limit: int | |
| task_backoff_seconds: int | |
| browser_page_timeout: int | |
| logs_page_size: int | |
| selenium_error_limit: int | |
| selenium_restart_limit: int | |
| submit_captcha_retry_limit: int | |
| chrome_binary: str | |
| chromedriver_path: str | |
| def load(cls) -> "AppConfig": | |
| root_dir = Path(__file__).resolve().parent.parent | |
| data_dir = Path(os.getenv("DATA_DIR", str(root_dir / "data"))).resolve() | |
| data_dir.mkdir(parents=True, exist_ok=True) | |
| super_admin_username = os.getenv("ADMIN", "superadmin") | |
| super_admin_password = os.getenv("PASSWORD", "change-me-in-hf-space") | |
| session_secret, encryption_key = _load_or_create_internal_secrets(data_dir) | |
| return cls( | |
| root_dir=root_dir, | |
| data_dir=data_dir, | |
| db_path=data_dir / "course_catcher.db", | |
| session_secret=session_secret, | |
| encryption_key=encryption_key, | |
| super_admin_username=super_admin_username, | |
| super_admin_password=super_admin_password, | |
| default_parallel_limit=DEFAULT_PARALLEL_LIMIT, | |
| poll_interval_seconds=POLL_INTERVAL_SECONDS, | |
| login_retry_limit=LOGIN_RETRY_LIMIT, | |
| task_backoff_seconds=TASK_BACKOFF_SECONDS, | |
| browser_page_timeout=BROWSER_PAGE_TIMEOUT, | |
| logs_page_size=LOGS_PAGE_SIZE, | |
| selenium_error_limit=SELENIUM_ERROR_LIMIT, | |
| selenium_restart_limit=SELENIUM_RESTART_LIMIT, | |
| submit_captcha_retry_limit=SUBMIT_CAPTCHA_RETRY_LIMIT, | |
| chrome_binary=_pick_binary( | |
| "CHROME_BIN", | |
| "/usr/bin/chromium", | |
| "/usr/bin/chromium-browser", | |
| "chromium", | |
| "chromium-browser", | |
| ), | |
| chromedriver_path=_pick_binary( | |
| "CHROMEDRIVER_PATH", | |
| "/usr/bin/chromedriver", | |
| "chromedriver", | |
| ), | |
| ) | |