from dataclasses import dataclass @dataclass class CatchupConfig: enabled: bool = True max_age_minutes: int = 120 per_run_limit: int = 50 first_run_lookback_minutes: int = 30 max_failure_retries: int = 10 @dataclass class GroupConfig: require_mention: bool = False tools: dict | None = None system_prompt: str | None = None class BlueBubblesConfigAdapter: CHANNEL_KEY = "bluebubbles" def _get_channel_config(self, config: dict) -> dict: return config.get("channels", {}).get(self.CHANNEL_KEY, {}) def list_account_ids(self, config: dict) -> list[str]: channel_config = self._get_channel_config(config) accounts = channel_config.get("accounts", {}) if accounts: return list(accounts.keys()) return ["default"] async def resolve_account(self, config: dict, account_id: str = "default") -> dict | None: channel_config = self._get_channel_config(config) accounts = channel_config.get("accounts", {}) account = accounts.get(account_id, {}) if account_id != "default" else {} server_url = account.get("serverUrl") or channel_config.get("serverUrl") password = account.get("password") or channel_config.get("password") if not server_url or not password: return None return { "account_id": account_id, "server_url": server_url, "password": password, "dm_policy": account.get("dmPolicy") or channel_config.get("dmPolicy", "pairing"), "allow_from": account.get("allowFrom") or channel_config.get("allowFrom", []), "group_policy": account.get("groupPolicy") or channel_config.get("groupPolicy", "allowlist"), "group_allow_from": account.get("groupAllowFrom") or channel_config.get("groupAllowFrom", []), "groups": {**channel_config.get("groups", {}), **account.get("groups", {})}, "media_max_mb": account.get("mediaMaxMb") or channel_config.get("mediaMaxMb"), "media_local_roots": account.get("mediaLocalRoots") or channel_config.get("mediaLocalRoots", []), "text_chunk_limit": account.get("textChunkLimit") or channel_config.get("textChunkLimit", 4000), "chunk_mode": account.get("chunkMode") or channel_config.get("chunkMode", "newline"), "send_timeout_ms": account.get("sendTimeoutMs") or channel_config.get("sendTimeoutMs", 30000), "send_read_receipts": account.get("sendReadReceipts", channel_config.get("sendReadReceipts", True)), "coalesce_same_sender_dms": account.get( "coalesceSameSenderDms", channel_config.get("coalesceSameSenderDms", False) ), "enrich_group_participants_from_contacts": account.get( "enrichGroupParticipantsFromContacts", channel_config.get("enrichGroupParticipantsFromContacts", True), ), "allow_private_network": account.get("network", {}).get("dangerouslyAllowPrivateNetwork") or channel_config.get("network", {}).get("dangerouslyAllowPrivateNetwork", False), "catchup": CatchupConfig(**{**channel_config.get("catchup", {}), **account.get("catchup", {})}), "webhook_path": account.get("webhookPath") or channel_config.get("webhookPath", "/bluebubbles-webhook"), "webhook_secret": account.get("webhookSecret") or channel_config.get("webhookSecret", ""), "enabled": account.get("enabled", channel_config.get("enabled", True)), "config": {**channel_config, **account}, } def is_configured(self, account: dict | None) -> bool: return bool(account and account.get("server_url") and account.get("password"))