import json import threading from pathlib import Path from typing import Any, Callable, Dict DEFAULT_STATE: Dict[str, Any] = { "api_id": None, "api_hash": None, "channels": {}, "channel_names": {}, "scrape_media": True, "forwarding_rules": [], "continuous_scraping": { "enabled": True, "interval_minutes": 1, "channels": [], "run_all_tracked": True, }, } class StateStore: def __init__(self, path: Path): self.path = path self.lock = threading.RLock() def load(self) -> Dict[str, Any]: with self.lock: if not self.path.exists(): return self._default_state() try: with self.path.open("r", encoding="utf-8") as handle: state = json.load(handle) except (json.JSONDecodeError, OSError): return self._default_state() return self._merge_defaults(state) def save(self, state: Dict[str, Any]) -> None: with self.lock: self.path.parent.mkdir(parents=True, exist_ok=True) tmp_path = self.path.with_suffix(self.path.suffix + ".tmp") with tmp_path.open("w", encoding="utf-8") as handle: json.dump(self._merge_defaults(state), handle, ensure_ascii=False, indent=2) handle.write("\n") tmp_path.replace(self.path) def update(self, mutator: Callable[[Dict[str, Any]], None]) -> Dict[str, Any]: with self.lock: state = self.load() mutator(state) self.save(state) return state def continuous_config(self) -> Dict[str, Any]: state = self.load() return dict(state.get("continuous_scraping") or DEFAULT_STATE["continuous_scraping"]) def save_continuous_config(self, config: Dict[str, Any]) -> Dict[str, Any]: def mutate(state: Dict[str, Any]) -> None: state["continuous_scraping"] = { "enabled": bool(config.get("enabled", True)), "interval_minutes": max(1, int(config.get("interval_minutes", 1) or 1)), "channels": [ str(item).strip() for item in config.get("channels", []) if str(item).strip() ], "run_all_tracked": bool(config.get("run_all_tracked", True)), } return self.update(mutate)["continuous_scraping"] def _default_state(self) -> Dict[str, Any]: return json.loads(json.dumps(DEFAULT_STATE)) def _merge_defaults(self, state: Dict[str, Any]) -> Dict[str, Any]: merged = self._default_state() for key, value in state.items(): if key == "continuous_scraping" and isinstance(value, dict): merged[key].update(value) else: merged[key] = value return merged