import os import logging from yuxi.channel.extensions.intercom.types import IntercomAccount logger = logging.getLogger(__name__) class IntercomConfigAdapter: def list_account_ids(self, config: dict) -> list[str]: accounts = config.get("channels", {}).get("intercom", {}).get("accounts", {}) if accounts: return list(accounts.keys()) if os.getenv("INTERCOM_ACCESS_TOKEN"): return ["default"] return [] async def resolve_account(self, account_id: str, config: dict | None = None) -> dict: account = self._resolve_account_dataclass(account_id, config or {}) return { "account_id": account.account_id, "access_token": account.access_token, "webhook_secret": account.webhook_secret, "admin_id": account.admin_id, "dm_policy": account.dm_policy, "allow_from": account.allow_from, "auto_skip_closed": account.auto_skip_closed, "auto_skip_snoozed": account.auto_skip_snoozed, "handoff_policy": account.handoff_policy, "datacenter": account.datacenter, } def _resolve_account_dataclass(self, account_id: str, config: dict) -> IntercomAccount: channel_cfg = config.get("channels", {}).get("intercom", {}) accounts = channel_cfg.get("accounts", {}) account_cfg = accounts.get(account_id, {}) if accounts else channel_cfg access_token = self._resolve_token(account_cfg.get("accessToken"), "INTERCOM_ACCESS_TOKEN") webhook_secret = self._resolve_token(account_cfg.get("webhookSecret"), "INTERCOM_WEBHOOK_SECRET") admin_id = account_cfg.get("adminId") or os.getenv("INTERCOM_ADMIN_ID", "") return IntercomAccount( account_id=account_id, access_token=access_token, webhook_secret=webhook_secret, admin_id=admin_id, dm_policy=account_cfg.get("dmPolicy", "open"), allow_from=account_cfg.get("allowFrom", []), auto_skip_closed=account_cfg.get("autoSkipClosed", True), auto_skip_snoozed=account_cfg.get("autoSkipSnoozed", True), handoff_policy=account_cfg.get("handoffPolicy", "ignore"), datacenter=account_cfg.get("datacenter", "us"), ) @staticmethod def _resolve_token(account_value: str | None, env_key: str) -> str: if account_value is not None: if account_value == "none": return "" if account_value.startswith("$"): env_val = os.environ.get(account_value.strip("${}"), "") return env_val return account_value return os.environ.get(env_key, "") def is_configured(self, account: dict) -> bool: return bool(account.get("access_token")) def is_enabled(self, account: dict, config: dict) -> bool: return True def disabled_reason(self, account: dict, config: dict) -> str: return "" def unconfigured_reason(self, account: dict, config: dict) -> str: if self.is_configured(account): return "" return "Missing required credential: accessToken" def describe_account(self, account: dict, config: dict) -> dict: return { "account_id": account.get("account_id", ""), "dm_policy": account.get("dm_policy", "open"), "handoff_policy": account.get("handoff_policy", "ignore"), } def default_account_id(self, config: dict) -> str: return config.get("channels", {}).get("intercom", {}).get("default_account", "default") def resolve_allow_from(self, config: dict, account_id: str | None = None) -> list[str] | None: channel_cfg = config.get("channels", {}).get("intercom", {}) accounts = channel_cfg.get("accounts", {}) if account_id and account_id in accounts: return accounts[account_id].get("allowFrom") return channel_cfg.get("allowFrom") def format_allow_from(self, config: dict, account_id: str | None, allow_from: list[str | int]) -> list[str]: return [str(entry) for entry in allow_from] def has_configured_state(self, config: dict) -> bool: accounts = config.get("channels", {}).get("intercom", {}).get("accounts", {}) if accounts: for account_cfg in accounts.values(): if account_cfg.get("accessToken") or account_cfg.get("accessToken") is None: return True return bool(os.getenv("INTERCOM_ACCESS_TOKEN")) def has_persisted_auth_state(self, config: dict) -> bool: accounts = config.get("channels", {}).get("intercom", {}).get("accounts", {}) if accounts: for account_cfg in accounts.values(): if account_cfg.get("accessToken"): return True return False def config_schema(self) -> dict: return { "$schema": "https://json-schema.org/draft-07/schema#", "type": "object", "title": "Intercom 渠道配置", "properties": { "accounts": { "type": "object", "title": "账户列表", "description": "Intercom 账户配置,key 为账户 ID", "additionalProperties": { "type": "object", "properties": { "accessToken": { "type": "string", "title": "Access Token", "description": "Intercom Access Token (Settings > Developer Hub > Access Tokens)", "x-ui-password": True, }, "webhookSecret": { "type": "string", "title": "Webhook Secret", "description": "用于验证 Webhook 请求的 HMAC 密钥", "x-ui-password": True, }, "adminId": { "type": "string", "title": "Admin ID", "description": "Bot 的管理员 ID(留空则自动从 API 获取)", }, "dmPolicy": { "type": "string", "title": "DM 安全策略", "enum": ["open", "pairing", "allowlist", "disabled"], "default": "open", }, "allowFrom": { "type": "array", "title": "白名单 Contact ID", "items": {"type": "string"}, "description": "仅 dmPolicy=allowlist 时生效,格式: contact_xxx 或 lead_xxx", }, "autoSkipClosed": { "type": "boolean", "title": "跳过已关闭会话", "default": True, }, "autoSkipSnoozed": { "type": "boolean", "title": "跳过已暂停会话", "default": True, }, "handoffPolicy": { "type": "string", "title": "人工接管策略", "enum": ["ignore", "note_only", "continue"], "default": "ignore", "description": "检测到人工接管后的行为", }, "datacenter": { "type": "string", "title": "数据中心", "enum": ["us", "eu", "au"], "default": "us", "description": "Intercom 数据中心区域", }, }, "required": ["accessToken"], }, }, "default_account": { "type": "string", "title": "默认账户", "default": "default", }, }, }