Files
Solo_bot/api/routes/modules.py
T
2026-02-14 13:02:59 +03:00

110 lines
3.4 KiB
Python

import pkgutil
from pathlib import Path
from typing import Literal
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from api.depends import verify_admin_token
from utils.modules_loader import _is_safe_module_name
from utils.modules_manager import manager
router = APIRouter(prefix="/modules", tags=["Modules"])
MODULES_DIR = Path(__file__).resolve().parents[2] / "modules"
class ModuleAction(BaseModel):
action: Literal["start", "stop", "restart"]
def _available_module_names() -> list[str]:
candidates: set[str] = set()
for name in manager.registry.keys():
if name:
candidates.add(name)
candidates.update(manager.disabled)
if MODULES_DIR.is_dir():
for _finder, name, _ispkg in pkgutil.iter_modules([str(MODULES_DIR)]):
name = (name or "").strip()
if name and _is_safe_module_name(name):
candidates.add(name)
return sorted(n for n in candidates if _is_safe_module_name(n))
def _module_state(name: str) -> dict:
normalized = name.strip()
record = manager.registry.get(normalized)
is_enabled = manager.is_enabled(normalized)
return {
"name": normalized,
"enabled": is_enabled,
"loaded": bool(record and record.enabled),
"autostart": manager.should_autostart(normalized),
}
def _read_local_module_version(name: str) -> str | None:
version_file = MODULES_DIR / name / "VERSION"
if not version_file.exists() or not version_file.is_file():
return None
try:
with version_file.open("r", encoding="utf-8") as handle:
for line in handle:
value = line.strip()
if value:
return value
except Exception:
return None
return None
@router.get("/")
async def list_modules(admin=Depends(verify_admin_token)):
refresh = getattr(manager, "refresh_state", None)
if callable(refresh):
refresh()
else:
legacy_refresh = getattr(manager, "_load_state", None)
if callable(legacy_refresh):
legacy_refresh()
module_names = _available_module_names()
modules = [_module_state(name) for name in module_names]
for item in modules:
name = str(item.get("name") or "").strip()
local_version = _read_local_module_version(name)
item["local_version"] = local_version
return {"items": modules}
@router.post("/{module_name}/actions")
async def control_module(module_name: str, payload: ModuleAction, admin=Depends(verify_admin_token)):
name = (module_name or "").strip()
if not _is_safe_module_name(name):
raise HTTPException(status_code=404, detail="Module not found")
try:
if payload.action == "start":
await manager.start(name)
elif payload.action == "stop":
await manager.stop(name)
elif payload.action == "restart":
await manager.restart(name)
else:
raise HTTPException(status_code=400, detail="Unsupported action")
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
except RuntimeError as exc:
raise HTTPException(status_code=500, detail=str(exc)) from exc
return {"item": _module_state(name)}