import json from pathlib import Path from typing import Any, Callable, NotRequired, TypedDict, cast from utils import get_bundle_dir class ConfigData(TypedDict): discord_user_id: NotRequired[str] volume: NotRequired[int] Validator = Callable[[Any], bool] Normalizer = Callable[[Any], Any] class ConfigField(TypedDict): default: Any validator: Validator normalizer: Normalizer CONFIG_PATH = get_bundle_dir() / "config.json" DISCORD_USER_KEY = "discord_user_id" VOLUME_KEY = "volume" CONFIG_SCHEMA: dict[str, ConfigField] = { DISCORD_USER_KEY: { "default": "", "validator": lambda value: isinstance(value, str), "normalizer": lambda value: str(value).strip(), }, VOLUME_KEY: { "default": 30, "validator": lambda value: isinstance(value, int) and 0 <= value <= 100, "normalizer": lambda value: max(0, min(int(value), 100)), }, } class ConfigManager: def __init__(self, path: Path | None = None) -> None: self.path = path or CONFIG_PATH self._data: ConfigData = self._load() self._dirty = False # Lecture du fichier de configuration def _load(self) -> ConfigData: if not self.path.exists(): return {} try: with self.path.open("r", encoding="utf-8") as file: data = json.load(file) except (json.JSONDecodeError, OSError): return {} if not isinstance(data, dict): return {} return cast(ConfigData, data) # Sauvegarde du fichier de configuration def save(self) -> None: if not self._dirty: return self.path.parent.mkdir(parents=True, exist_ok=True) with self.path.open("w", encoding="utf-8") as file: json.dump(self._data, file, indent=4, ensure_ascii=False) self._dirty = False def _get_field(self, key: str) -> ConfigField: if key not in CONFIG_SCHEMA: raise KeyError(f"Unknown config key: {key}") return CONFIG_SCHEMA[key] def get(self, key: str) -> Any: field = self._get_field(key) value = self._data.get(key, field["default"]) if not field["validator"](value): return field["default"] return value def set(self, key: str, value: Any) -> None: field = self._get_field(key) normalized = field["normalizer"](value) if not field["validator"](normalized): raise ValueError(f"Invalid value for {key}") if self._data.get(key) == normalized: return self._data[key] = normalized self._dirty = True def reset_all(self) -> None: defaults: ConfigData = cast( ConfigData, {key: field["default"] for key, field in CONFIG_SCHEMA.items()}, ) self.save(defaults) def get_all(self) -> ConfigData: return cast( ConfigData, {key: self.get(key) for key in CONFIG_SCHEMA}, ) # --------------------------------------------------------------------------- # SETTERS MÉTIER # --------------------------------------------------------------------------- # Set Discord ID def set_discord_user(self, user_id: str) -> None: self.set(DISCORD_USER_KEY, user_id) # Set volume def set_volume(self, volume: int) -> None: self.set(VOLUME_KEY, volume) # --------------------------------------------------------------------------- # GETTERS MÉTIER # --------------------------------------------------------------------------- # Get discord ID def get_default(self, key: str): return CONFIG_SCHEMA[key]["default"] # Get volume value def get_discord_user(self) -> str: return cast(str, self.get(DISCORD_USER_KEY)) def get_volume(self) -> int: return cast(int, self.get(VOLUME_KEY))