from __future__ import annotations from dataclasses import dataclass, field from typing import Any @dataclass class MatrixAccountPatch: user_id: str | None = None display_name: str | None = None avatar_url: str | None = None homeserver: str | None = None access_token: str | None = None device_id: str | None = None dm_policy: str | None = None group_policy: str | None = None allow_bots: bool | None = None auto_join: str | None = None sync_timeout_ms: int | None = None loop_sleep_ms: int | None = None probe_timeout_s: float | None = None response_prefix: str | None = None require_mention: bool | None = None chunk_mode: str | None = None media_max_mb: int | None = None crypto_store_dir: str | None = None rooms: dict[str, Any] | None = None dm_allow_from: list[str] | None = None group_allow_from: list[str] | None = None extra: dict[str, Any] = field(default_factory=dict) _KNOWN_FIELDS = frozenset( { "user_id", "display_name", "avatar_url", "homeserver", "access_token", "device_id", "dm_policy", "group_policy", "allow_bots", "auto_join", "sync_timeout_ms", "loop_sleep_ms", "probe_timeout_s", "response_prefix", "require_mention", "chunk_mode", "media_max_mb", "crypto_store_dir", "rooms", "dm_allow_from", "group_allow_from", } ) def to_dict(self) -> dict[str, Any]: result: dict[str, Any] = {} for name in self._KNOWN_FIELDS: value = getattr(self, name) if value is not None: result[name] = value result.update(self.extra) return result @classmethod def from_dict(cls, data: dict[str, Any]) -> MatrixAccountPatch: known: dict[str, Any] = {} extra: dict[str, Any] = {} for k, v in data.items(): if k in cls._KNOWN_FIELDS: known[k] = v else: extra[k] = v return cls(**known, extra=extra) @property def has_changes(self) -> bool: return any(getattr(self, name) is not None for name in self._KNOWN_FIELDS) or bool(self.extra) def apply_patch(config: dict[str, Any], patch: MatrixAccountPatch) -> dict[str, Any]: result = dict(config) fields = patch.to_dict() for key, value in fields.items(): if key in ("rooms", "dm_allow_from", "group_allow_from"): if isinstance(value, (list, dict)): result[key] = value else: result[key] = value return result def merge_patches(base: MatrixAccountPatch, overlay: MatrixAccountPatch) -> MatrixAccountPatch: base_dict = base.to_dict() overlay_dict = overlay.to_dict() base_dict.update(overlay_dict) return MatrixAccountPatch.from_dict(base_dict) def diff_config(old: dict[str, Any], new: dict[str, Any]) -> dict[str, tuple[Any, Any]]: all_keys = set(old.keys()) | set(new.keys()) diff: dict[str, tuple[Any, Any]] = {} for key in all_keys: old_val = old.get(key) new_val = new.get(key) if old_val != new_val: diff[key] = (old_val, new_val) return diff