ForcePilot/backend/package/yuxi/channel/extensions/intercom/config.py

193 lines
8.5 KiB
Python
Raw Normal View History

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",
},
},
}