from __future__ import annotations import re from enum import Enum from typing import Optional class RouteAccessMode(str, Enum): PUBLIC = "public" PANEL_ONLY = "panel_only" BOT_OR_PANEL = "bot_or_panel" PUBLIC_BOT_OR_PANEL = "public_bot_or_panel" _BOT_ID_API_RE = re.compile(r"^/api/bots/([^/]+)(?:/.*)?$") _BOT_ID_PUBLIC_RE = re.compile(r"^/public/bots/([^/]+)(?:/.*)?$") _BOT_PANEL_ONLY_ROUTE_METHODS = [ (re.compile(r"^/api/bots/[^/]+$"), {"DELETE"}), (re.compile(r"^/api/bots/[^/]+/(?:enable|disable|deactivate)$"), {"POST"}), ] _PUBLIC_PATHS = { "/api/panel/auth/status", "/api/panel/auth/login", "/api/panel/auth/logout", "/api/health", "/api/health/cache", "/api/system/defaults", } _BOT_PUBLIC_AUTH_RE = re.compile(r"^/api/bots/[^/]+/auth/(?:login|logout|status)$") def extract_bot_id(path: str) -> Optional[str]: raw = str(path or "").strip() match = _BOT_ID_API_RE.match(raw) or _BOT_ID_PUBLIC_RE.match(raw) if not match or not match.group(1): return None return match.group(1).strip() or None def resolve_route_access_mode(path: str, method: str) -> RouteAccessMode: raw_path = str(path or "").strip() verb = str(method or "GET").strip().upper() if raw_path in _PUBLIC_PATHS or _BOT_PUBLIC_AUTH_RE.fullmatch(raw_path): return RouteAccessMode.PUBLIC if raw_path.startswith("/public/bots/"): return RouteAccessMode.PUBLIC_BOT_OR_PANEL if _BOT_ID_API_RE.fullmatch(raw_path): if any(pattern.fullmatch(raw_path) and verb in methods for pattern, methods in _BOT_PANEL_ONLY_ROUTE_METHODS): return RouteAccessMode.PANEL_ONLY return RouteAccessMode.BOT_OR_PANEL if raw_path.startswith("/api/"): return RouteAccessMode.PANEL_ONLY return RouteAccessMode.PUBLIC