from __future__ import annotations import json from copy import deepcopy from pathlib import Path DEFAULT_TEMPLATES: dict[str, dict] = { "reminder_halftime": { "text": "Half-time in 5 minutes!", "color": 0xE67E22, }, "halftime": { "text": "Half-time!", "color": 0x2ECC71, "image_url": "https://copyparty.allucanget.biz/img/weed.png", }, "reminder": { "text": "This is your 5 minute reminder to 420!", "color": 0xE67E22, }, "420": { "text": "Blaze it!", "color": 0x2ECC71, "image_url": "https://copyparty.allucanget.biz/img/weed.png", }, } def _normalize_templates(raw: dict) -> dict[str, dict]: out: dict[str, dict] = deepcopy(DEFAULT_TEMPLATES) if not isinstance(raw, dict): return out for key, default in DEFAULT_TEMPLATES.items(): incoming = raw.get(key) if not isinstance(incoming, dict): continue text = incoming.get("text") if isinstance(text, str): out[key]["text"] = text color = incoming.get("color") if isinstance(color, int): out[key]["color"] = color image_url = incoming.get("image_url") if isinstance(image_url, str) and image_url.strip(): out[key]["image_url"] = image_url.strip() elif "image_url" in default and image_url in (None, ""): # Allow clearing image_url only if explicitly set to empty. out[key].pop("image_url", None) return out def load_templates(path: str | Path) -> dict[str, dict]: p = Path(path) try: if not p.exists(): return deepcopy(DEFAULT_TEMPLATES) raw = json.loads(p.read_text(encoding="utf-8")) return _normalize_templates(raw) except Exception: return deepcopy(DEFAULT_TEMPLATES) def save_templates(path: str | Path, templates: dict) -> None: p = Path(path) normalized = _normalize_templates(templates) p.parent.mkdir(parents=True, exist_ok=True) tmp = p.with_suffix(p.suffix + ".tmp") tmp.write_text(json.dumps(normalized, indent=2, sort_keys=True) + "\n", encoding="utf-8") tmp.replace(p) def parse_color(value: str) -> int: """Parse color from '#RRGGBB', 'RRGGBB', '0xRRGGBB', or decimal.""" s = (value or "").strip().lower() if not s: raise ValueError("color is required") if s.startswith("#"): s = s[1:] base = 16 if s.startswith("0x"): s = s[2:] elif all(c.isdigit() for c in s): base = 10 color = int(s, base) if color < 0 or color > 0xFFFFFF: raise ValueError("color must be between 0 and 0xFFFFFF") return color