"""Google 로그인 설정과 분리한다. 서비스 계정 키는 서버 파일로만 읽는다.""" import os from dataclasses import dataclass from urllib.parse import urlsplit @dataclass(frozen=True) class SearchConsoleSettings: property_url: str credentials_file: str alert_url: str = "" alert_days: int = 7 def load_settings() -> SearchConsoleSettings | None: if os.environ.get("GSC_ENABLED") != "1": return None prop = os.environ.get("GSC_PROPERTY_URL", "").strip() key_file = os.environ.get("GSC_CREDENTIALS_FILE", "").strip() if not prop or not key_file: raise ValueError("GSC_CONFIG_MISSING") parsed = urlsplit(prop) domain_property = prop.startswith("sc-domain:") and bool(prop.removeprefix("sc-domain:")) root_property = (parsed.scheme == "https" and parsed.hostname and parsed.path == "/" and not parsed.query and not parsed.fragment and not parsed.username) if not domain_property and not root_property: raise ValueError("GSC_PROPERTY_INVALID") days = int(os.environ.get("GSC_ALERT_DAYS", "7")) if days < 1: raise ValueError("GSC_ALERT_DAYS_INVALID") return SearchConsoleSettings(prop, key_file, os.environ.get("GSC_ALERT_WEBHOOK_URL", ""), days) def belongs_to_property(page_url: str, property_url: str) -> bool: parsed = urlsplit(page_url) if parsed.scheme != "https" or not parsed.hostname or parsed.username or parsed.password: return False if parsed.query or parsed.fragment: return False if property_url.startswith("sc-domain:"): domain = property_url.removeprefix("sc-domain:").lower() return bool(domain) and (parsed.hostname == domain or parsed.hostname.endswith("." + domain)) return page_url.startswith(property_url)