refactor(config): Settings i JsonFileStore jako podstawa pakietu

Początek pakietu zyrarbot, budowanego wedle zasady jedna klasa = jeden plik.

- Settings zbiera konfigurację ze zmiennych środowiskowych oraz ścieżki
  i limity, które dotąd były literałami rozsianymi po bot.py i web.py
  ("config/config.json", "uploads", zakres 1-999, domyślne 5 odtworzeń).
- JsonFileStore zastępuje sześć niemal identycznych bloków open/json.load
  z ręcznym try/except. Brakujący lub uszkodzony plik daje pusty słownik
  zamiast wyjątku, a zapis sam tworzy katalog.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
lgooseandClaude Bot committed 2026-08-21 01:09:52 +02:00
1 parent edf317d21e
commit e194a8611c
4 files changed
+102

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
"""ŻyrarBOT - bot muzyczny Discord z panelem webowym."""
__all__ = ["__version__"]
__version__ = "2.0.0"
+43
View File
@@ -0,0 +1,43 @@
from __future__ import annotations
import os
from dataclasses import dataclass
from dotenv import load_dotenv
@dataclass(frozen=True)
class Settings:
"""Konfiguracja aplikacji odczytana ze zmiennych środowiskowych."""
discord_token: str | None = None
guild_id: int | None = None
config_dir: str = "config"
uploads_dir: str = "uploads"
static_dir: str = "static"
web_host: str = "0.0.0.0"
web_port: int = 8000
default_plays: int = 5
history_limit: int = 50
min_plays: int = 1
max_plays: int = 999
@classmethod
def from_env(cls) -> Settings:
load_dotenv()
raw_guild_id = os.getenv("GUILD_ID")
return cls(
discord_token=os.getenv("DISCORD_TOKEN"),
guild_id=int(raw_guild_id) if raw_guild_id else None,
)
@property
def config_file(self) -> str:
return os.path.join(self.config_dir, "config.json")
@property
def history_file(self) -> str:
return os.path.join(self.config_dir, "history.json")
def is_valid_plays(self, value: int) -> bool:
return self.min_plays <= value <= self.max_plays
+1
View File
@@ -0,0 +1 @@
"""Trwałe składowanie danych: pliki JSON oraz katalog uploadów."""
+53
View File
@@ -0,0 +1,53 @@
from __future__ import annotations
import json
import logging
import os
from typing import Any
logger = logging.getLogger(__name__)
class JsonFileStore:
"""Prosty magazyn słownika w pliku JSON, odporny na brakujący/uszkodzony plik."""
def __init__(self, path: str):
self._path = path
@property
def path(self) -> str:
return self._path
def read(self) -> dict[str, Any]:
if not os.path.exists(self._path):
return {}
try:
with open(self._path, "r", encoding="utf-8") as handle:
data = json.load(handle)
except (OSError, ValueError) as error:
logger.error("Nie udało się odczytać %s: %s", self._path, error)
return {}
return data if isinstance(data, dict) else {}
def write(self, data: dict[str, Any]) -> bool:
directory = os.path.dirname(self._path)
try:
if directory:
os.makedirs(directory, exist_ok=True)
with open(self._path, "w", encoding="utf-8") as handle:
json.dump(data, handle, indent=4, ensure_ascii=False)
except OSError as error:
logger.error("Nie udało się zapisać %s: %s", self._path, error)
return False
return True
def set(self, key: str, value: Any) -> bool:
data = self.read()
if value is None:
data.pop(key, None)
else:
data[key] = value
return self.write(data)
def get(self, key: str, default: Any = None) -> Any:
return self.read().get(key, default)