from __future__ import annotations import os class IMessageConfigAdapter: CHANNEL_KEY = "imessage" 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", {}) return list(accounts.keys()) if accounts else ["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, {}) server_url = ( account.get("server_url") or channel_config.get("server_url") or os.getenv("IMESSAGE_SERVER_URL", "") ) password = ( account.get("password") or channel_config.get("password") or os.getenv("IMESSAGE_SERVER_PASSWORD", "") ) if not server_url or not password: return None return { "account_id": account_id, "server_url": server_url, "password": password, "name": account.get("name", account_id), "dm_policy": account.get("dm_policy", channel_config.get("dm_policy", "pairing")), "allow_from": account.get("allow_from", channel_config.get("allow_from", [])), "group_policy": account.get("group_policy", channel_config.get("group_policy", "allowlist")), "group_allow_from": account.get("group_allow_from", channel_config.get("group_allow_from", [])), "media_max_mb": account.get("media_max_mb", channel_config.get("media_max_mb", 16)), "text_chunk_limit": account.get("text_chunk_limit", channel_config.get("text_chunk_limit", 4000)), "streaming": account.get("streaming", channel_config.get("streaming", {})), "config": {**channel_config, **account}, } def is_configured(self, account: dict | None) -> bool: return bool(account and account.get("server_url") and account.get("password")) def is_enabled(self, account: dict | None) -> bool: if account is None: return False return account.get("config", {}).get("enabled", True) if account else False def disabled_reason(self, account: dict | None) -> str: if not account: return "Not configured" if not account.get("server_url"): return "Missing server URL" if not account.get("password"): return "Missing password" return "" async def resolve_allow_from(self, config: dict, account_id: str) -> list[str] | None: account = await self.resolve_account(config, account_id) if not account: return None return account.get("allow_from", []) def describe_account(self, account: dict) -> dict: return { "account_id": account.get("account_id", ""), "name": account.get("name", ""), "configured": self.is_configured(account), "server_url": account.get("server_url", ""), } def config_schema(self) -> dict: return { "type": "object", "properties": { "enabled": {"type": "boolean", "default": True}, "accounts": { "type": "object", "additionalProperties": { "type": "object", "properties": { "server_url": {"type": "string", "description": "BlueBubbles server URL"}, "password": {"type": "string", "description": "BlueBubbles server password"}, "name": {"type": "string"}, "dm_policy": { "type": "string", "enum": ["pairing", "allowlist", "open", "disabled"], "default": "pairing", }, "allow_from": { "type": "array", "items": {"type": "string"}, "description": "E.164 numbers or emails allowed to DM", }, "group_policy": { "type": "string", "enum": ["open", "allowlist", "disabled"], "default": "allowlist", }, "group_allow_from": { "type": "array", "items": {"type": "string"}, }, "media_max_mb": {"type": "integer", "default": 16}, "text_chunk_limit": {"type": "integer", "default": 4000}, "streaming": { "type": "object", "properties": { "mode": {"type": "string", "enum": ["block"], "default": "block"}, "min_chars": {"type": "integer", "default": 2000}, "coalesce_min_chars": {"type": "integer", "default": 400}, }, }, }, }, }, "default_account": {"type": "string", "default": "default"}, "groups": { "type": "object", "description": "Per-group config overrides keyed by chat GUID", "additionalProperties": { "type": "object", "properties": { "require_mention": {"type": "boolean", "description": "Require @mention to trigger"}, "group_policy": { "type": "string", "enum": ["open", "allowlist", "disabled"], }, "system_prompt": {"type": "string", "description": "Group-specific system prompt"}, }, }, }, "streaming": { "type": "object", "description": "Default streaming settings", "properties": { "mode": {"type": "string", "enum": ["block"], "default": "block"}, "min_chars": {"type": "integer", "default": 2000}, "coalesce_min_chars": {"type": "integer", "default": 400}, }, }, }, }